Lisp makrolarını bu kadar özel yapan nedir?


298

Paul Graham'ın programlama dilleri üzerine makalelerini okumak , Lisp makrolarının tek yol olduğunu düşünebilir . Meşgul bir geliştirici olarak diğer platformlarda çalışarak Lisp makrolarını kullanma ayrıcalığım olmadı. Vızıltıyı anlamak isteyen biri olarak, lütfen bu özelliği neyin bu kadar güçlü kıldığını açıklayın.

Lütfen bunu Python, Java, C # veya C geliştirme dünyalarından anlayabileceğim bir şeyle de ilişkilendirin.


2
Bu arada, C # için LeMP adlı bir LISP tarzı makro işlemci var: ecsharp.net/lemp ... JavaScript ayrıca Sweet.js: sweetjs.org
Qwertie

@Qwertie Sweetjs bugünlerde çalışıyor mu?
fredrik.hjarner

Kullanmadım ama en son taahhüt altı ay önceydi ... benim için yeterince iyi!
Qwertie

Yanıtlar:


308

Kısa cevap vermek için, Ortak Lisp veya Etki Alanına Özel Diller (DSL) için dil sözdizimi uzantılarını tanımlamak için makrolar kullanılır. Bu diller mevcut Lisp kodunun içine yerleştirilmiştir. Şimdi, DSL'lerin Lisp'e benzeyen bir sözdizimi (Peter Norvig'in Ortak Lisp için Prolog Tercümanı gibi ) veya tamamen farklı olabilir (örn . Clojure için Infix Notation Math ).

İşte daha somut bir örnek:
Python, dilde yerleşik liste anlayışlarına sahiptir. Bu, genel bir durum için basit bir sözdizimi sağlar. Çizgi

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

0 ile 9 arasındaki tüm çift sayıları içeren bir liste verir. Python'da 1,5 gün içinde böyle bir sözdizimi yoktu; bunun gibi bir şey kullanırsınız:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

Bunların her ikisi de işlevsel olarak eşdeğerdir. Güvensizliği askıya almamızı isteyelim ve Lisp'in sadece yineleme yapan ve liste kavrayışlarının eşdeğerini yapmanın kolay bir yolu olmayan çok sınırlı bir döngü makrosuna sahip olduğunu varsayalım.

Lisp'de aşağıdakileri yazabilirsiniz. Bu örnek, Lisp kodunun iyi bir örneği değil Python koduyla özdeş olarak seçilmiştir.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

Daha ileri gitmeden önce, bir makronun ne olduğunu daha iyi açıklamalıyım. Kod üzerinde koda göre yapılan bir dönüşümdür . Yani, yorumlayıcı (veya derleyici) tarafından okunan ve bir argüman olarak kodu alan bir kod parçası, manipüle eder ve sonucu döndürür ve daha sonra yerinde çalıştırılır.

Tabii ki bu çok fazla yazım ve programcılar tembel. Böylece DSL'yi liste kavrayışı yapmak için tanımlayabiliriz. Aslında, zaten bir makro kullanıyoruz (döngü makrosu).

Lisp birkaç özel sözdizimi formu tanımlar. 'Tırnak işareti ( ), bir sonraki belirtecin değişmez olduğunu belirtir. Quasiquote veya backtick ( `), bir sonraki belirtecin kaçan bir değişmez olduğunu gösterir. Kaçışlar virgül operatörü tarafından belirtilir. Gerçek '(1 2 3)Python's eşdeğerdir [1, 2, 3]. Başka bir değişkene atayabilir veya yerinde kullanabilirsiniz. Aklınıza gelebilecek `(1 2 ,x)Python eşdeğer olarak [1, 2, x]nerede xdeğişken önceden tanımlanır. Bu liste gösterimi, makrolara giren büyünün bir parçasıdır. İkinci bölüm, akıllıca kod yerine makroları değiştiren Lisp okuyucusudur, ancak en iyi aşağıda gösterilmiştir:

Böylece, lcomp(listeyi anlama kısaltması) adlı bir makro tanımlayabiliriz . Sözdizimi tam olarak örnekte kullandığımız python gibi olacak [x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

Şimdi komut satırında çalışabiliriz:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

Çok temiz, ha? Şimdi burada bitmiyor. İsterseniz bir mekanizmanız veya bir boya fırçanız var. İstediğiniz herhangi bir sözdizimine sahip olabilirsiniz. Python veya C # withsözdizimi gibi. Veya .NET'in LINQ sözdizimi. Sonunda, insanları Lisp'e çeken şey budur - nihai esneklik.


51
Lisp'te liste kavrayışı uygulamak için +1, çünkü neden olmasın?
ckb

9
: @ckb Aslında LISP zaten standart kütüphanede bir liste anlama makro var (loop for x from 0 below 10 when (evenp x) collect x), burada daha fazla örnek . Ama gerçekten, döngü "sadece bir makro" (aslında bir süre önce sıfırdan yeniden uyguladım )
Suzanne Dupéron

8
Oldukça ilgisiz olduğunu biliyorum, ancak sözdizimi ve ayrıştırma işleminin gerçekten nasıl çalıştığını merak ediyorum ... lcomp'ı bu şekilde çağırıyorum (bu öğeyi "için" için "azertyuiop" olarak değiştirme): (lcomp x azertyuiop x in ( aralık 10) eğer (= (% x 2) 0)) makro yine de beklendiği gibi çalışır mı? Veya döngüde "for" parametresi çağrıldığında "for" dizesi olması için mi kullanılır?
dader

2
@dader Yani, döngü aslında başka bir makro ve for için de bu makroda kullanılan özel bir semboldür. Döngü makrosu olmadan makroyu kolayca tanımlayabilirdim. Ancak, olsaydım, görev çok daha uzun olurdu. Döngü makrosu ve tüm sözdizimi CLtL'de tanımlanır .
gte525u

3
Diğer dilin makrolarıyla karıştırdığım bir şey, makrolarının ana bilgisayar dilinin sözdizimi ile sınırlı olmasıdır. Lispy makroları Lispy olmayan sözdizimini yorumlar. Demek istediğim bir haskell benzeri sözdizimi oluşturmayı (parantez yok) ve Lisp makrolarını kullanarak yorumlamayı hayal et. Bu mümkün mü ve doğrudan bir lexer ve ayrıştırıcı kullanmaya kıyasla makro kullanmanın artıları / eksileri nelerdir?
CMCDragonkai

105

Burada lisp makrosu hakkında kapsamlı bir tartışma bulacaksınız .

Bu makalenin ilginç bir alt kümesi:

Çoğu programlama dilinde, sözdizimi karmaşıktır. Makrolar program sözdizimini ayırmalı, analiz etmeli ve yeniden birleştirmelidir. Programın ayrıştırıcısına erişimleri yoktur, bu nedenle sezgisel tarama ve en iyi tahminlere bağlı olmalıdırlar. Bazen kesme oranı analizleri yanlış olur ve sonra kırılırlar.

Ama Lisp farklı. Lisp makrolar yapmak ayrıştırıcı erişebilir ve bu gerçekten basit ayrıştırıcısıdır. Lisp makrosuna dize verilmez, ancak Lisp programının kaynağı bir dize olmadığı için, liste biçiminde hazırlanmış bir kaynak kodu parçası verilir; bu bir listedir. Lisp programları listelerin parçalanması ve bir araya getirilmesinde gerçekten çok iyi. Bunu her gün güvenilir bir şekilde yapıyorlar.

İşte genişletilmiş bir örnek. Lisp'de atama yapan "setf" adlı bir makro vardır. Setf'in en basit şekli

  (setf x whatever)

"x" sembolünün değerini "ne olursa olsun" ifadesinin değerine ayarlar.

Lisp'in de listeleri var; bir listenin ilk öğesini veya listenin geri kalanını almak için "car" ve "cdr" işlevlerini kullanabilirsiniz.

Şimdi bir listenin ilk öğesini yeni bir değerle değiştirmek isterseniz ne olur? Bunu yapmak için standart bir işlev vardır ve inanılmaz bir şekilde, adı "araba" dan daha da kötüdür. "Rplaca" dır. Ama "rplaca" yı hatırlamak zorunda değilsiniz, çünkü yazabilirsiniz

  (setf (car somelist) whatever)

Somelistin arabasını kurmak için.

Burada gerçekten olan şey, "setf" in bir makro olmasıdır. Derleme zamanında, argümanlarını inceler ve ilkinin formuna sahip olduğunu görür (SOMETHING arabası). Kendi kendine "Oh, programcı bir şey arabasını ayarlamaya çalışıyor. Bunun için kullanılacak işlev" rplaca "dır. Ve sessizce yerinde kodu yeniden yazar:

  (rplaca somelist whatever)

5
setf makroların gücünün güzel bir örneğidir, dahil ettiğiniz için teşekkürler.
Joel

Vurgulamayı seviyorum ... çünkü bir Lisp programının kaynağı bir dize değildir; bu bir listedir. ! LISP makrosunun parantezleri nedeniyle diğerlerinin çoğundan daha üstün olmasının ana nedeni mi?
Öğrenci

@ Öğrenci sanırım: books.google.fr/… haklı olduğunu gösteriyor.
VonC

55

Yaygın Lisp makroları esasen kodunuzun "sözdizimsel ilkellerini" genişletir.

Örneğin, C'de, switch / case yapısı yalnızca ayrılmaz türlerle çalışır ve bunu kayan satırlar veya dizeler için kullanmak istiyorsanız, if ifadeleri ve açık karşılaştırmalar ile iç içe kalırsınız. İşi sizin için yapmak için C makrosu yazmanın da bir yolu yoktur.

Ancak, bir lisp makrosu (esasen) kod parçacıklarını girdi olarak alan ve makronun "çağrılmasını" değiştirmek için kod döndüren bir lisp programı olduğundan, "ilkel" repertuarınızı istediğiniz kadar uzatabilirsiniz, genellikle sona erer daha okunabilir bir program ile.

Aynı şeyi C'de yapmak için, ilk (oldukça-C değil) kaynağınızı yiyen ve bir C derleyicisinin anlayabileceği bir şey veren özel bir ön işlemci yazmanız gerekir. Bu konuda yanlış bir yol değil, ama mutlaka en kolayı değil.


41

Lisp makroları, herhangi bir parçanın veya ifadenin ne zaman (tümüyle) değerlendirileceğine karar vermenizi sağlar. Basit bir örnek vermek gerekirse, C'leri düşünün:

expr1 && expr2 && expr3 ...

Bunun söylediği şudur: Değerlendirin expr1ve doğru olması durumunda değerlendirin expr2.

Şimdi bunu &&bir fonksiyon haline getirmeye çalışın ... bu doğru, yapamazsınız. Şöyle bir şey çağırıyor:

and(expr1, expr2, expr3)

Yanlış exprsolup olmadığına bakılmaksızın bir cevap vermeden önce her üçünü de değerlendirecek expr1!

Lisp makroları ile şöyle kodlayabilirsiniz:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

artık &&bir fonksiyonunuz gibi çağırabileceğiniz ve hepsi doğru olmadıkça ona geçtiğiniz formları değerlendirmeyecek bir.

Bunun ne kadar yararlı olduğunu görmek için, kontrast:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

ve:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Makrolarla yapabileceğiniz diğer şeyler, yeni anahtar kelimeler ve / veya mini diller oluşturmak ( (loop ...)örneğin makroyu kontrol etmek ), diğer dilleri lisp'e entegre etmek, örneğin, şöyle bir şey söylemenizi sağlayan bir makro yazabilirsiniz:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

Ve bu Reader makrolarına bile girmiyor .

Bu yardımcı olur umarım.


Bence şöyle olmalı: "(&&, @ exprs uygulayın); bu ve yanlış olabilir!"
Svante

1
@svante - iki açıdan: ilk olarak && bir işlev değil bir makro; sadece fonksiyonlarda çalışır. ikinci olarak, uygulamak için bir argüman listesi alın, böylece "(funcall fn, @ exprs)", "(fn (list, @ exprs uygula)" veya "(fn, @ exprs nil uygula)" "(fn, @ exprs uygula)"
Aaron

(and ...biri yanlış olarak değerlendirilene kadar ifadeleri değerlendirir, yanlış değerlendirmenin oluşturduğu yan etkilerin gerçekleşeceğini, yalnızca sonraki ifadelerin atlanacağını unutmayın.
ocodo


10

Makrolar ve şablonlarla C veya C ++ ile neler yapabileceğinizi düşünün. Tekrarlayan kodları yönetmek için çok kullanışlı araçlardır, ancak oldukça ciddi yollarla sınırlıdırlar.

  • Sınırlı makro / şablon sözdizimi kullanımlarını kısıtlar. Örneğin, sınıf veya işlev dışında bir şeye genişleyen bir şablon yazamazsınız. Makrolar ve şablonlar dahili verileri kolayca koruyamaz.
  • C ve C ++ 'ın karmaşık, çok düzensiz sözdizimi, çok genel makroların yazılmasını zorlaştırır.

Lisp ve Lisp makroları bu sorunları çözer.

  • Lisp makroları Lisp'te yazılmıştır. Makroyu yazmak için Lisp'in tüm gücüne sahipsiniz.
  • Lisp çok düzenli bir sözdizimine sahiptir.

C ++ konusunda uzman olan herkesle konuşun ve onlara şablon meta programlaması yapmak için ihtiyaç duydukları tüm şablon akışını öğrenmek için ne kadar zaman harcadıklarını sorun. Ya da Modern C ++ Design gibi (mükemmel) kitaplarda , hata on yıl boyunca hala standartlaştırılmış olmasına rağmen, gerçek dünya derleyicileri arasında hata ayıklamak ve (pratikte) taşınabilir olmayan tüm çılgın numaralar . Meta programlama için kullandığınız dil programlama için kullandığınız dil aynıysa, bunların hepsi erir!


11
Adil olmak gerekirse, C ++ şablon meta programlaması ile ilgili sorun, metaprogramlama dilinin farklı olması değil, korkunç olması - çok daha basit bir şablon işlevselliği olması amaçlanan şeyde keşfedildiği gibi tasarlanmamıştı.
Brooks Moses

@Brooks Sure. Ortaya çıkan özellikler her zaman kötü değildir. Ne yazık ki, yavaş hareket eden bir komite odaklı dilde, bunları düzeltmek zordur. C ++ 'ın modern kullanışlı yeni özelliklerinin birçoğunun bile okuyabileceği bir dilde yazılması çok utanç verici ve ortalama programcı ve "yüksek rahip" arasında büyük bir boşluk var.
Matt Curtis

2
@downvoter: Cevabımda bir sorun varsa, lütfen bilgiyi paylaşabilmemiz için bir yorum bırakın.
Matt Curtis

10

Lisp makrosu bir program parçasını girdi olarak alır. Bu program parçası, istediğiniz gibi manipüle edilebilen ve dönüştürülebilen bir veri yapısını temsil eder. Sonunda makro başka bir program parçası çıkarır ve bu parça çalışma zamanında yürütülür.

C # bir makro özelliği yoktur, ancak eşdeğer bir kod derleyici bir CodeDOM ağacına ayrıştırmak ve bunu başka bir CodeDOM dönüştürülen, daha sonra IL derlenmiş bir yönteme geçirdi olurdu.

Bu , temel koda dönüşen makrolar olarak for each-statement using-clause, linq -expressions selectve benzeri gibi "şeker" sözdizimini uygulamak için kullanılabilir .

Java'da makrolar varsa, temel dili değiştirmek için Sun'a gerek duymadan Java'da Linq sözdizimini uygulayabilirsiniz.

İşte uygulamak için C # lisp tarzı bir makro usingnasıl görünebilir için sözde kod :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

Şimdi aslında oradaki olduğunu C # için Lisp tarzı makro işlemci, ben bir makro işaret olur usingediyorum şu şekilde görünür ;)
Qwertie

9

Mevcut cevaplar, makroların neyi başardığını ve nasıl elde ettiğini açıklayan iyi somut örnekler verdiğinden, makro tesisin diğer dillere göre neden önemli bir kazanç olduğu hakkındaki düşüncelerin bir araya gelmesine yardımcı olabilir ; önce bu cevaplardan, sonra başka yerlerden harika bir cevaptan:

... C'de, muhtemelen yeterince karmaşık bir C programı olarak nitelendirilecek özel bir ön işlemci yazmanız gerekir ...

- Vatin

C ++ konusunda uzman olan herkesle konuşun ve onlara şablon meta programlaması yapmak için ihtiyaç duydukları tüm şablon fudrasiyi öğrenmek için ne kadar zaman harcadıklarını sorun.

- Matt Curtis

... Java'da bytecode dokuma ile yolunuzu kesmelisiniz, AspectJ gibi bazı çerçeveler bunu farklı bir yaklaşımla yapmanıza izin veriyor, ancak temelde bir kesmek.

- Miguel Ping

DOLIST, Perl'in foreach veya Python's için benzer. Java, JSR-201'in bir parçası olarak Java 1.5'teki döngü için "gelişmiş" ile benzer bir döngü yapısı ekledi. Makroların ne fark ettiğine dikkat edin. Kodlarında ortak bir örüntü fark eden bir Lisp programcısı, kendilerine bu örüntünün kaynak düzeyinde bir soyutlamasını vermek için bir makro yazabilir. Aynı modeli fark eden bir Java programcısı, Sun'ı bu özel soyutlamanın dile eklemeye değer olduğuna ikna etmelidir. Daha sonra Sun'ın bir JSR yayınlaması ve her şeyi ortaya çıkarmak için sektör çapında bir "uzman grubu" toplaması gerekiyor. Sun'a göre bu süreç ortalama 18 ay sürüyor. Bundan sonra, derleyici yazarlarının hepsi yeni özelliği desteklemek için derleyicilerini yükseltmek zorundadır. Ve Java programcısının favori derleyicisi Java'nın yeni sürümünü desteklese bile, muhtemelen '' hala '', Java'nın eski sürümleriyle kaynak uyumluluğunu kırmasına izin verilmedikçe yeni özelliği kullanamazlar. Bu nedenle, Common Lisp programcılarının beş dakika içinde kendileri için çözebilecekleri bir sıkıntı Java programcılarını yıllarca rahatsız ediyor.

- Peter Seibel, "Pratik Ortak Lisp"


8

Herkesin (mükemmel) yayınlarına bir fikir ekleyebileceğimden emin değilim, ama ...

Lisp makroları, Lisp sözdizimi niteliği nedeniyle harika çalışır.

Lisp son derece düzenli bir dildir (her şeyin bir liste olduğunu düşünün ); makrolar, verileri ve kodu aynı şekilde ele almanızı sağlar (lisp ifadelerini değiştirmek için dize ayrıştırma veya başka saldırılara gerek yoktur). Bu iki özelliği birleştiriyorsunuz ve kodu değiştirmek için çok temiz bir yolunuz var.

Düzenleme: Ne söylemeye çalışıyordum Lisp homoikonik , yani bir lisp programı için veri yapısı lisp kendisi yazılır anlamına gelir.

Böylece, tüm koduyla dilin kendisini kullanarak dilin üzerinde kendi kod üretecinizi yaratmanın bir yolunu bulursunuz (örneğin, Java'da baytkod dokuma ile yolunuzu kesmek zorundasınız, ancak AspectJ gibi bazı çerçeveler Bunu farklı bir yaklaşım kullanarak yapın, temelde bir kesmek).

Uygulamada, makrolarla , ek dil veya araç öğrenmeye gerek kalmadan ve dilin tüm gücünü kullanarak kendi mini dilinizi lisp üzerine inşa edersiniz .


1
Bununla birlikte, "her şeyin bir liste olduğu" fikri yeni gelenleri korkutabilir. bir listeyi anlamak için eksilerini, arabaları, cdrs, hücreleri anlamanız gerekir. Daha doğrusu, Lisp S-expressionslistelerden değil , yapılır .
18'de ribamar

6

Lisp makroları hemen hemen her büyük programlama projesinde ortaya çıkan bir modeli temsil eder. Sonunda büyük bir programda, daha sonra yapıştırabileceğiniz kaynak kodunu metin olarak çıkaran bir program yazmanızın daha basit ve daha az hataya meyilli olacağını fark ettiğiniz belirli bir kod bölümüne sahipsiniz.

Python nesnelerinde iki yöntem __repr__ve vardır __str__. __str__sadece insan tarafından okunabilir sunumdur. __repr__geçerli Python kodu olan bir temsili döndürür, yani yorumlayıcıya geçerli Python olarak girilebilecek bir şey döndürür. Bu şekilde, gerçek kaynağınıza yapıştırılabilen geçerli kod üreten küçük Python snippet'leri oluşturabilirsiniz.

Lisp'te bu süreç makro sistem tarafından resmileştirildi. Elbette sözdizimi için uzantılar oluşturmanıza ve her türlü süslü şeyi yapmanıza izin verir, ancak gerçek kullanışlılığı yukarıdakilerle özetlenir. Tabii ki Lisp makro sistemi bu "parçacıkları" tüm dilin tam gücüyle değiştirmenize izin verir.


1
İlk paragrafınız Lisp yabancılarına çok açıktır, ki bu önemlidir.
Wildcard

5

Kısacası, makrolar kodun dönüşümleridir. Birçok yeni sözdizimi yapısının kullanılmasına izin veriyorlar. Örneğin, C # içinde LINQ düşünün. Lisp'te, makrolar tarafından uygulanan benzer dil uzantıları vardır (örneğin, yerleşik döngü yapısı, yineleme). Makrolar kod çoğaltmayı önemli ölçüde azaltır. Makrolar «küçük diller» yerleştirmeye izin verir (örneğin, c # / java'da yapılandırmak için xml kullanılırsa, lisp içinde aynı şey makrolarla elde edilebilir). Makrolar, kitaplık kullanımındaki zorlukları gizleyebilir.

Örneğin, lisp'de yazabilirsiniz

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

ve bu, tüm veritabanı öğelerini (işlemler, uygun bağlantı kapanışı, veri getirme vb.) gizlerken, C # 'da bu SqlConnections, SqlCommands oluşturmayı, SqlComarasers'ı SqlCommands'a eklemeyi, SqlDataReaders'ı döngülerek, düzgün kapatmayı gerektirir.


3

Her şeyden önce makroların ne olduğunu ve hatta harika örneklere sahip olduğunu açıklasa da, bir makro ve normal bir işlev arasındaki temel fark, LISP'nin işlevi çağırmadan önce tüm parametreleri değerlendirmesidir. Bir makro ile bunun tersi, LISP parametreleri değerlendirmeden makroya geçirir. Örneğin, bir işleve (+ 1 2) iletirseniz, işlev 3 değerini alır. Bunu bir makroya iletirseniz, bir Liste (+ 1 2) alır. Bu, her türlü inanılmaz derecede faydalı şeyler yapmak için kullanılabilir.

  • Döngü veya listenin yapısökümü gibi yeni bir kontrol yapısı ekleme
  • Aktarılan bir işlevin yürütülmesi için geçen süreyi ölçün. Bir işlevle, parametre işleve geçirilmeden önce parametre değerlendirilir. Makro ile kodunuzu kronometrenizin başlangıcı ve durması arasında birleştirebilirsiniz. Aşağıdakiler bir makroda ve fonksiyonda tam olarak aynı koda sahiptir ve çıktı çok farklıdır. Not: Bu tartışmalı bir örnektir ve uygulama, farkı daha iyi vurgulamak için aynı olacak şekilde seçilmiştir.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

Btw, Scala dile makrolar ekledi. Lisp makrolarının güzelliğinden yoksun olsalar da, dil homoikonik olmadığı için kesinlikle ona bakmaya değer ve sağladıkları soyut sözdizimi ağaçlarının kullanımı daha kolay olabilir. Hangi makro sistemini tercih ettiğimi söylemek için henüz erken.
Joerg Schmuecker

2
“LISP parametreleri değerlendirmeden makroya geçirir” nihayet açıkça söyleyen bir cevap. ancak bunun ikinci yarısını unuttun: ve bir makronun sonucu, sistem tarafından orijinal olarak yerine ilk sıradaymış gibi değerlendirilecek dönüştürülmüş bir koddur . bu kez bu makro tarafından dönüştürülecek bir makro).
Ness Ness

0

Bunu ortak lisp yemek kitabından aldım ama lisp makrolarının neden güzel bir şekilde iyi olduğunu düşünüyorum.

"Makro, başka bir varsayılan Lisp kodu parçası üzerinde çalışan ve yürütülebilir Lisp'e (daha yakın bir sürüm) çevrilen sıradan bir Lisp kodudur. Bu biraz karmaşık gelebilir, bu yüzden basit bir örnek verelim. iki değişkeni aynı değere ayarlayan setq sürümü.

(setq2 x y (+ z 3))

ne zaman z=8x ve her iki y 11'e ayarlanır (Bunun için herhangi bir kullanım düşünemiyorum, ama sadece bir örnek.)

Setq2'yi bir işlev olarak tanımlayamayacağımız açıktır. x=50Ve y=-5, bu işlev 50, -5 ve 11 değerlerini alırsa ; hangi değişkenlerin ayarlanması gerektiği konusunda hiçbir bilgisi olmayacaktır. Gerçekten söylemek istediğimiz şey, Siz (Lisp sistemi) gördüğünüzde (setq2 v1 v2 e), ona eşdeğer olarak davranın (progn (setq v1 e) (setq v2 e)). Aslında, bu doğru değil, ama şimdilik olacak. Bir makro, girdi örüntüsünü (setq2 v1 v2 e)"çıktı örüntüsüne" dönüştürmek için bir program belirterek tam olarak bunu yapmamızı sağlar (progn ...).

Bunun güzel olduğunu düşünüyorsanız, okumaya devam edebilirsiniz: http://cl-cookbook.sourceforge.net/macros.html


1
Tanımlamak mümkündür setq2, eğer bir fonksiyonu olarak xve yreferans ile bu geçirilir. Ancak CL'de mümkün olup olmadığını bilmiyorum. Özellikle Lisps veya CL bilmeyen biri için bu çok açıklayıcı bir örnek değildir IMO
neoascetic

@neoascetic CL bağımsız değişkeni yalnızca değere göre değişir (bu yüzden öncelikle makrolara ihtiyaç duyar). bazı değerler işaretçilerdir (listeler gibi).
Ness Ness

-5

Python'da dekoratörleriniz var, temel olarak başka bir işlevi giriş olarak alan bir işleve sahipsiniz. İstediğinizi yapabilirsiniz: işlevi çağırın, başka bir şey yapın, işlev çağrısını bir kaynak edinme sürümüne sarın, vb. Ancak bu işlevin içine bakamazsınız. Diyelim ki daha güçlü hale getirmek istedik, dekoratörünüzün fonksiyonun kodunu bir liste olarak aldığını söyleyin, o zaman sadece işlevi olduğu gibi yürütmekle kalmayıp, aynı zamanda parçalarını yürütebilir, işlevin satırlarını yeniden sıralayabilirsiniz vb.

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.