Lisp makroları ne kadar faydalıdır?


22

Common Lisp, istediğiniz kaynak dönüşümünü yapan makroları yazmanıza izin verir.

Şema, dönüşümleri de yapmanıza izin veren hijyenik bir desen eşleştirme sistemi sunar. Makrolar pratikte ne kadar faydalıdır? Paul Graham , Ortalamaları Yenme’de şunları söyledi :

Viaweb editörünün kaynak kodu muhtemelen yaklaşık% 20-25 makro idi.

İnsanlar aslında makrolarla ne tür şeyler yaparlar?


Bence bu kesinlikle iyi bir öznelliğe dönüşüyor , biçimlendirme konusundaki sorunuzu düzelttim. Bu bir kopya olabilir , ama bir tane bulamadım.
Tim Post

1
Sanırım, tekrar eden her şey bir fonksiyona uymuyor gibi görünüyor.

2
Sen içine Lisp'i çevirmek için makro kullanabilirsiniz herhangi : Herhangi sözdizimi ve herhangi semantik ile diğer dilde bit.ly/vqqvHU
SK-mantık

programmers.stackexchange.com/questions/81202/… buraya bakmaya değer, ancak bu bir kopya değil.
David Thornley

Yanıtlar:


15

Bir göz atın bu gönderme O makrolar için üç ana kullanımlarını göstermektedir 2002 yılında listeyi tartışmak LL1 için Matthias Felleisen tarafından:

  1. Veri alt dilleri : Basit görünümlü ifadeler yazabilir ve makrolarla özenle hazırlanmış alıntı, alıntı, vb. İçeren karmaşık iç içe listeler / diziler / tablolar oluşturabilirim.
  2. Ciltleme yapıları : Makrolarla yeni ciltleme yapıları getirebilirim. Bu bana lambdalardan kurtulmamı ve birbirine ait olan şeyleri daha yakın yerleştirmemi sağlıyor.
  3. Değerlendirme sıralaması : İhtiyaç duyulduğunda ifadelerin değerlendirilmesini geciktiren / erteleyen yapıları tanıtabilirim. Döngüler, yeni şartlamalar, gecikme / kuvvet vb. Düşünün. [Not: Haskell veya tembel bir dilde, bu gerekli değildir.]

18

Çoğunlukla zaman kazandıran yeni dil yapıları eklemek için makrolar kullanırım, aksi halde bir demet kod kodu gerektirir.

Örneğin, son zamanlarda kendimi for-loopC ++ / Java'ya benzer bir zorunluluk arzusunda bulmaya başladım . Ancak, işlevsel bir dil olan Clojure, kutunun dışında biriyle gelmedi. Bu yüzden onu bir makro olarak uyguladım:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

Ve şimdi yapabilirim:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

Ve işte orada - altı satır kodda yeni bir genel amaçlı derleme zamanı dili yapısı.


13

İnsanlar aslında makrolarla ne tür şeyler yaparlar?

Dil uzantıları veya DSL yazma.

Lisp benzeri dilde çalışma bunun için bir fikir almak için Raket Yazılan Raket, R6RS ve DAtalog: Birkaç dili vardır, varyantları.

Makrolar üzerinden Etki Alanı-Özel Dilleri oluşturmak amacıyla derleyici boru hattına erişmenizi sağlayan Boo diline de bakınız .


4

İşte bazı örnekler:

Şema:

  • definefonksiyon tanımları için. Temel olarak bir işlevi tanımlamak için daha kısa bir yol yapar.
  • let sözlüksel kapsamda değişkenler oluşturmak için.

Clojure:

  • defn, dokümanlarına göre:

    Var metadata eklenmiş herhangi bir doc-string veya attr ile (def name (fn [params *] exprs *)) veya (def name (fn ([params *] exprs *) +)) ile aynı

  • for: anlama listeleri
  • defmacro: ironik?
  • defmethod, defmulti: Çoklu yöntemlerle gerçekleştirilen
  • ns

Bu makroların çoğu, daha soyut bir seviyede kod yazmayı çok kolaylaştırıyor. Makroların birçok yönden Lisps olmayan sözdizimine benzer olduğunu düşünüyorum.

Çizim kitaplığı Incanter , bazı karmaşık veri manipülasyonları için makrolar sağlar.


4

Makrolar, bazı kalıpları gömmek için kullanışlıdır.

Örneğin, Common Lisp, whiledöngüyü tanımlamaz ancak tanımlamak için do kullanılabilecek olanı tanımlar.

İşte On Lisp'tan bir örnek .

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

Bu "12345678910" yazdıracaktır ve ne olduğunu görmeye çalışırsanız macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

Bu geri dönecek:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

Bu basit bir makrodur, ancak daha önce de belirtildiği gibi, genellikle yeni dilleri veya DSL'leri tanımlamak için kullanılırlar, ancak bu basit örnekten, onlarla neler yapabileceğinizi hayal etmeye çalışabilirsiniz.

loopMakro makro neler yapabileceğini iyi bir örnektir.

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Yaygın Lisp, okuyucunun kodu nasıl yorumlayacağını değiştirmek için kullanılabilecek, okuyucu makrosu adı verilen başka bir makro türüne sahiptir , yani # # ve # # gibi sınırlayıcıları kullanmak için bunları kullanabilirsiniz.


3

İşte hata ayıklama için kullandığım (Clojure'da):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

C ++ 'ta elle haddelenmiş bir karma tabloyla uğraşmak zorunda kaldım, buradaki getmetot argüman olarak const olmayan bir dize başvurusu aldı; Başa çıkmayı kolaylaştırmak için aşağıdaki gibi bir şey yazdım:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

Bu tür bir sorunun lisp'te ortaya çıkması pek mümkün olmamakla birlikte, özellikle gerçek bir izin-bağlatarak , argümanlarını iki kez değerlendirmeyen makrolara sahip olmanız çok hoş . (Kabul ettim, işte bunun etrafında olabilirdi).

Ben de bir çirkin şeyleri kullanabilmeniz için çok çirkin bir hack paketine başvuruyorum do ... while (false)ve hala beklendiği gibi başka bir parça çalışmasını yapıyorum. Buna lisan'da ihtiyacınız yoktur, bu karakter dizileri yerine sözdizimi ağaçlarında çalışan makroların bir işlevidir (ya da token dizileri, sanırım C ve C ++ durumunda) ve sonra ayrıştırma işlemine tabi tutulur.

Kodunuzu daha temiz bir şekilde okuyacak şekilde yeniden düzenlemek için kullanabileceğiniz birkaç yerleşik iş parçacığı makroları vardır (paralelliği değil, kodunuzu bir arada eklerken olduğu gibi 'iş parçacığı'). Örneğin:

(->> (range 6) (filter even?) (map inc) (reduce *))

İlk formu alır ve bir (range 6)sonraki formun (filter even?)son argümanı olan sonraki formun son argümanını oluşturur, böylece yukarıdakiler yeniden yazılır.

(reduce * (map inc (filter even? (range 6))))

Sanırım ilk önce çok daha net bir şekilde okuyor: "bu verileri al, bunu yap, sonra bunu yap, sonra diğerini yap ve biz bittik", ama bu öznel; nesnel olarak doğru olan bir şey, işlemleri gerçekleştirildikleri sırayla okumanızdır (tembellikten yoksun).

Ayrıca önceki formu ilk (son değil) argüman olarak ekleyen bir değişken de vardır. Bir kullanım durumu aritmetik:

(-> 17 (- 2) (/ 3))

"17 almak, 2 çıkarmak ve 3 ile bölmek" olarak okuyor.

Aritmetikten bahsedersek, notasyon ayrıştırmayı düzenleyen bir makro yazabilir, böylece örneğin söyleyebildiğiniz (infix (17 - 2) / 3)ve (/ (- 17 2) 3)daha az okunabilir olmanın dezavantajının ve geçerli bir lisp ifadesi olmanın avantajının tükürdüğünü söyleyebilirsiniz . Bu DSL / veri alt dilinin bir kısmı.


1
İşlev uygulaması benim için diş açmaya göre çok daha anlamlı, ama kesinlikle bir alışkanlık meselesi. Güzel cevap
coredump
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.