lisp-in-rs-macros, yalnızca Rust'ın deklaratif makroları ile çalışan basit bir leksik kapsamlı Lisp yorumlayıcısıdır ve lisp! makrosu kodu derleme zamanında değerlendirerek stringleştirilmiş bir Lisp değeri üretir
lisp!(CAR (CONS (QUOTE A) (QUOTE (B)))), rustc'nin makro genişletme sürecinde hesaplanır ve "A" dizgesine genişler; tüm uygulama 250 satırdan azdır
- Örnekler
CAR, LIST, QUOTE, PROGN, DEFINE, LAMBDA, DISPLAY kullanır; quine örneği ise Lisp kodunun kendisine değerlendiği bir yapıyı gösterir
- Açık özyineleme şu anda desteklenmiyor, ancak self application ile liste birleştirme gibi özyinelemeli davranışlar yazılabiliyor; buna karşın
DEFINE kendi başına özyinelemeli tanımları işlemiyor
- Meta-circular yorumlayıcı örneği çalışıyor gibi görünüyor, ancak
((lambda (X) X) (quote a)) değerlendirmesi 30 saniyeden uzun sürüyor ve bir milyondan fazla token üreterek cargo'nun sigkill almasına yol açacak kadar verimsiz kalıyor
Rust makrolarının içinde çalışan Lisp
lisp-in-rs-macros, yalnızca Rust'ın deklaratif makroları ile yazılmış, leksik kapsamlı bir Lisp yorumlayıcısıdır
lisp! makrosu, kendisine verilen Lisp kodunu değerlendirir ve ardından hesaplanan Lisp değerini stringleştirir
- Örneğin
lisp!(CAR (CONS (QUOTE A) (QUOTE (B)))), "A" dizgesine genişler
- Bu hesaplama çalışma zamanında değil, rustc'nin makroları genişlettiği derleme zamanında gerçekleşir
- Uygulama 250 satırdan azdır
Temel kullanım örneği
CAR, LIST, QUOTE birleştirilerek bir listenin ilk öğesi alınabilir
let output = lisp!(CAR (LIST (QUOTE A) (QUOTE B) (QUOTE C)));
assert_eq!(output, "A");
- Birden fazla ifadeyi değerlendirmek için
PROGN kullanılır
PROGN, tüm ifadeleri değerlendirir ve son ifadenin değerini döndürür
DISPLAY, önce argümanını değerlendirir, ardından println!("{}", stringify!(evaled_argument)) biçiminde genişleyerek token'ları stringleştirip yazdırır
lisp!(PROGN
(DEFINE message (LAMBDA () (QUOTE "hello there")))
(DISPLAY (message))
(DEFINE NOT (LAMBDA (X) (COND (X NIL) (TRUE TRUE))) )
(DISPLAY (NOT NIL))
);
- Yukarıdaki örnek
"hello there" ve "TRUE" çıktısını üretir
Kendisine değerlendirilen quine
- Quine örneği, Lisp kodunun kendisine değerlendiği bir yapıyı gösterir
lisp!
((LAMBDA (s) (LIST s (LIST (QUOTE QUOTE) s)))
(QUOTE (LAMBDA (s) (LIST s (LIST (QUOTE QUOTE) s)))));
- Bu kod, aşağıdaki
stringify! çağrısına genişler
stringify!(((LAMBDA (s) (LIST s (LIST (QUOTE QUOTE) s)))
(QUOTE (LAMBDA (s) (LIST s (LIST (QUOTE QUOTE) s))))));
Özyineleme ve self application
- Bu Lisp şu anda açık özyinelemeyi desteklemiyor
- Açık özyineleme olmadan da yalnızca lambda ile özyinelemeli davranış üretilebiliyor
- Örnekteki
append fonksiyonu, gövdesinde doğrudan append adını anmadan, self argümanı üzerinden kendini uygulayarak özyinelemeli çağrı yapıyor
lisp!(PROGN
(DEFINE append
(LAMBDA (self X Y)
(COND
((EQ X NIL) Y)
(TRUE (CONS (CAR X) (self self (CDR X) Y)))
)))
(append append (QUOTE (A B)) (QUOTE (C D)))
)
- Bu kod
"(A B C D)" sonucunu üretir
Kullanım kısıtları
lisp! makrosu yalnızca tek bir ifadeyi değerlendirir
- Birden fazla ifade
(PROGN expr1 expr2 expr3) ile gruplanmalıdır
- Boş liste self-evaluating değildir
- Boş liste değeri
NIL veya (QUOTE ()) ile elde edilebilir
- Boş liste, tek falsy nesnedir
- Dotted list desteklenmez
CONS, son argümanın bir liste olduğunu varsayar
DEFINE her yerde kullanılabilir ve boş listeye değerlendirilir, ancak özyineleme desteklemez
TRUE, fonksiyon olmayan atomlar arasında self-evaluating olan tek atomdur
Desteklenen formlar
DEFINE
QUOTE
LAMBDA
LET
PROGN
CAR
CDR
CONS
LIST
EQ
ATOM
APPLY
DEFINE, gerçek Lisp tarzı özyinelemeli tanımdan ziyade Scheme'deki iç tanımlara daha yakındır
Lisp ile yazılmış Lisp yorumlayıcısı
- Depoda, bu Lisp üzerinde yazılmış bir meta-circular yorumlayıcı örneği bulunuyor
- Örnek; iki argümanlı
Y2 kombinatörü, CADR, CAAR, ASSOC, eval gibi tanımlar içeriyor
- Yorumlayıcı çalışıyor gibi görünse de,
((lambda (X) X) (quote a)) ifadesini değerlendirmeye çalıştığında 30 saniyeden uzun sürüyor
- Bu değerlendirme bir milyondan fazla token üretiyor ve sonunda cargo, sigkill alacak kadar büyüyor
- Açık Y kombinatoryle yapılan özyineleme burada özellikle verimsiz
- Bunu düzeltmek için açık bir özyineleme primitive'i eklenmesi gerektiği belirtiliyor
- Meta-circular değerlendirici yazımına dair walkthrough olarak Paul Graham'ın
"Roots of Lisp" yazısı öneriliyor
Uygulama biçimi ve referanslar
- Teknik açıklama
EXPLANATION.md içinde yer alıyor
- Makrolar özünde bir SECD machine simüle ediyor
- SECD machine, lambda calculus terimlerini değerlendiren basit, yığın tabanlı bir soyut makinedir
Referanslar
Functional Programming: Application and Implementation by Peter Henderson
- Ager, Mads Sig, et al.
"A functional correspondence between evaluators and abstract machines."
The Implementation of Functional Programming Languages by Simon Peyton Jones
- Matt Might'ın Lisp ile ilgili blog yazıları: https://matt.might.net
TODO
letrec ekleme
- özyinelemeli
define ekleme
1 yorum
Hacker News yorumları
Greenspun'un onuncu kuralı yine karşımıza çıkmış: https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
Ancak C++26 ile
Args...[0]kullanarak tür isimli parametre paketinincar'ını alabiliyoruzNeden boş parametre paketleri için
nilvecar/cdrişlevleri ekleyip, şu anki sözdizimi keşmekeşi yerine parametre paketlerini saklanabilir hale getirmediklerini bilmiyorumEskiden benzer bir şey denemiştim ama içinde tire olan semboller tanımlanamıyordu
DEFINE MY-FN...gibi bir şey çalışmıyordu, çünkü Rust tirede token'ları bölüyorKüçük bir fark gibi ama gerçek Lisp kod parçalarını doğrudan yapıştırmak mümkün olmuyor, hepsini alt çizgiye çevirmek gerekiyor. Bunun da aynı durumda olup olmadığını merak ediyorum
$x:identile eşleştiriliyor ve bu yüzden atom içindeki tireler desteklenmiyorAma bunun yerine
$x:ident $(- $y:ident)*gibi bir şeyle eşleştirmek mümkün olabilir. Bazı makro dalı ayrıntılarını değiştirmek gerekir ama yapılabilir görünüyorDEFINE MYᜭFN...gayet çalışıyorSadece makro değil, Rust tabanlı iyi desteklenen bir Lisp uygulaması olsa güzel olurdu
Rust üzerinde yapılırsa bellek güvenliğinin ne kadarının korunacağı ya da kaybedileceği merak konusu. Ödünç alma denetleyicisini aklı başında bir şekilde kullanmak gerçekten mümkün olur mu?
Lisp genelde dinamik doğasıyla tanımlanır ve çalışma zamanı tür denetimi büyük yer tutar. Programcıyı nesne yönetim biçimiyle önceden ilgilenmeye zorlamak, böyle sistemlerde beklenen özgürlük ve ifade gücüyle çelişir
Öte yandan derleyicinin kendisi nispeten daha basit olabilir. Ek bildirim olmadan yazılmış sıradan kod temelde güvenlidir ve CLISP gibi bir bytecode sanal makinesinde ya da donanım tür denetimi yapan Lisp makinelerinde bu tür bildirimler yok sayılsa bile her zaman güvenli olabilir
SBCL kodu oldukça hızlı derliyor, başka uygulamaların daha da hızlı olduğu da söyleniyor. Buna karşılık Rust derleyicisi genç programcılara thrashing kavramını tanıtma ihtimali daha yüksek
Bana kalırsa bunlar, ilk bakışta öyle görünmese de, birbirleriyle pek uyuşmayan dünyalar. Lisp özünde “The Right Thing” felsefesinin amiral gemisi dilidir, C ise “Worse is Better” dilidir. Rust ise ikisi de değil; her iki felsefenin de kötü yanlarını yansıtan, bambaşka bir şey için yeni bir isim gerektirecek kadar farklı görünüyor
Bu arada bunu özgün yazıyı küçümsemek için söylemiyorum, yine de harika bir hack
Başka Lisp'ler de var (https://github.com/alilleybrinker/langs-in-rust). Yalnız daha az aktif biçimde bakımı yapılıyor gibiler
Bunu yaparken eğlendim ve ayrıca rust-analyser'ın milyonlarca token üreten makroları işleyemediğini de öğrendim
Herkesin “eğlenceli” diye tezahürat yapması bekleniyor ama ben böyle şeyler gördükçe Rust'ta bunun mümkün olmasından hoşlanmıyorum
Rust zaten baştan beri basit bir dil değildi ama şimdi ilk hâline göre çok daha zor taşınır bir şeye dönüşmüş gibi geliyor
Ama bunun mümkün olmasından neden hoşlanmadığını pek anlamıyorum. Makro sistemi neredeyse sınırsız karmaşıklıkta kod üretebilir, ama makrolarla sandbox'lanmış bir Lisp uygulamak, Rust'ın ilk günlerine göre yönetmesinin daha zor hâle geldiğine dair güçlü bir örnek mi, emin değilim
Öte yandan Rust'ın tür sisteminin C++ şablonları ya da Haskell tür sistemi gibi Turing-complete olması nedeniyle, o şekilde yazılmış bir Lisp'i de görmek isterdim
Bunun öne çıkan örnekleri non-lexical lifetimes, return-position
impl Traitve async trait. 1.0 öncesinde özel sözdizimine sahip GC referansları da yerleşik olarak vardı ama böyle özellikler kaldırıldıEğer ilke olarak sadeliği benimseyen bir dil istiyorsan, Rust zaten hiçbir zaman öyle bir dil değildi; başka pek çok seçenek var
Kontrol ettim ve bu bahsi ben kazandım: https://github.com/kchanqvq/CSP
Özellikle makro “yazma” kısmından söz ediyorum; bence kullanılması da kullanılmaması da mümkün olan ek bir özellik gibi
Vay canına, bu macro_rules kullanıyor
Ama C++'ın şablonları Turing-complete diye aklı başında bir dil olmadığını söylemiyor muyduk?
Rust makro sisteminin hangisine girdiğini bilmiyorum
macro_expandvar ve Rust araçlarının iyi yapılmış olması büyük fark yaratıyorCarp da anılmalı. Ödünç alma denetimi kullanan bir Lisp ve Lisp dünyasının “Rust”ı gibi
1: https://github.com/carp-lang/Carp