Lütfen Paul Graham'ın Lisp hakkındaki bazı noktalarını açıklayın


146

Paul Graham'ın What Made Lisp'i Farklı Yapan kitabından bazı noktaları anlamak için yardıma ihtiyacım var .

  1. Yeni bir değişken kavramı. Lisp'te tüm değişkenler etkili bir şekilde işaretçilerdir. Değerler, değişkenlere değil, türlere sahip olanlardır ve değişkenleri atamak veya bağlamak, işaret ettikleri şeyi değil, işaretçileri kopyalamak anlamına gelir.

  2. Bir sembol türü. Semboller, bir işaretçiyi karşılaştırarak eşitliği test edebilmeniz açısından dizelerden farklıdır.

  3. Sembol ağaçlarını kullanan kod için bir gösterim.

  4. Dilin tamamı her zaman mevcuttur. Okuma zamanı, derleme zamanı ve çalışma zamanı arasında gerçek bir ayrım yoktur. Kodu okurken derleyebilir veya çalıştırabilir, derleme sırasında kodu okuyabilir veya çalıştırabilir ve çalışma zamanında kodu okuyabilir veya derleyebilirsiniz.

Bu noktalar ne anlama geliyor? C veya Java gibi dillerde nasıl farklılar? Lisp aile dilleri dışında başka dillerde bu yapılardan herhangi biri şimdi var mı?


10
İşlevsel kod yazmak olduğu kadar birçok Lisps'de zorunlu veya OO kodu yazmak da eşit derecede mümkün olduğundan işlevsel programlama etiketinin burada garanti edildiğinden emin değilim - ve aslında çok sayıda işlevsel olmayan Lisp var etrafında kod. Fp etiketini kaldırmanızı ve bunun yerine clojure eklemenizi öneririm - umarım bu JVM tabanlı Lispers'tan bazı ilginç girdiler getirebilir.
Michał Marczyk

58
Ayrıca burada bir paul-grahametiketimiz var mı? !!! Harika ...
missingfaktor

@missingfaktor Belki de bir yanma isteğine
kedi

Yanıtlar:


99

Matt'in açıklaması gayet iyi - ve C ve Java ile karşılaştırma yapmak için bir çekim yapıyor ki bunu yapmayacağım - ama bazı nedenlerden dolayı bu konuyu arada bir tartışmaktan gerçekten keyif alıyorum, bu yüzden - işte benim çekim bir cevapta.

(3) ve (4) numaralı noktalarda:

Listenizdeki (3) ve (4) numaralı noktalar en ilginç ve şu anda hala alakalı görünüyor.

Bunları anlamak için, programcı tarafından yazılan bir karakter akışı biçiminde Lisp kodunun çalıştırılma yolunda ne olduğuna dair net bir resme sahip olmak yararlıdır. Somut bir örnek verelim:

;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])

;; this is the interesting bit:
(println (str/replace-re #"\d+" "FOO" "a123b4c56"))

Bu Clojure kod parçacığı yazdırılır aFOObFOOcFOO. Clojure'un muhtemelen listenizdeki dördüncü noktayı tam olarak karşılamadığını unutmayın, çünkü okuma zamanı gerçekten kullanıcı koduna açık değildir; Ancak bunun başka türlü olmasının ne anlama geleceğini tartışacağım.

Diyelim ki bu kodu bir yerde bir dosyada bulduk ve Clojure'dan onu çalıştırmasını istiyoruz. Ayrıca (basitlik adına) kütüphane içe aktarımını geçtiğimizi varsayalım. İlginç kısım en sağda başlar (printlnve biter ). Bu beklendiği gibi sözcüklü / ayrıştırılmış, ancak zaten önemli bir nokta ortaya çıkıyor: sonuç, derleyiciye özgü özel bir AST gösterimi değil - bu sadece normal bir Clojure / Lisp veri yapısı , yani bir grup sembol içeren iç içe geçmiş bir liste, dizeler ve - bu durumda - tek bir derlenmiş normal ifade desen nesnesi#"\d+"literal (bu konuda daha fazlası aşağıda). Bazı Lisp'ler bu sürece kendi küçük kıvrımlarını ekler, ancak Paul Graham çoğunlukla Common Lisp'ten bahsediyordu. Sorunuzla ilgili noktalarda Clojure, CL'ye benzer.

Derleme zamanında tüm dil:

Bu noktadan sonra, tüm derleyici ilgilenir (bu bir Lisp yorumlayıcısı için de geçerlidir; Clojure kodu her zaman derlenir), Lisp programcılarının işlemek için kullandıkları Lisp veri yapılarıdır. Bu noktada harika bir olasılık ortaya çıkıyor: Neden Lisp programcılarının Lisp programlarını temsil eden Lisp verilerini işleyen Lisp işlevleri yazmasına ve dönüştürülmüş programları temsil eden dönüştürülmüş veriyi orijinallerin yerine kullanmasına izin vermeyin? Başka bir deyişle - Lisp programcılarının, işlevlerini, Lisp'te makro adı verilen bir tür derleyici eklentisi olarak kaydetmesine neden izin vermiyorsunuz? Ve gerçekten de düzgün bir Lisp sistemi bu kapasiteye sahiptir.

Dolayısıyla, makrolar, gerçek nesne kodu yayınlandığında son derleme aşamasından önce, derleme zamanında programın gösterimi üzerinde çalışan normal Lisp işlevleridir. Kod makrolarının çalıştırılmasına izin verilen türlerde herhangi bir sınırlama olmadığından (özellikle çalıştırdıkları kod genellikle makro tesisinin liberal kullanımı ile yazılır), "tüm dil derleme zamanında kullanılabilir ".

Okuma zamanında tüm dil:

O #"\d+"normal ifadeye geri dönelim . Yukarıda bahsedildiği gibi, bu, derleyici derleme için hazırlanan yeni kodun ilk sözünü duymadan önce, okuma zamanında gerçek bir derlenmiş model nesnesine dönüştürülür. Bu nasıl olur?

Clojure'un şu anda uygulanma şekliyle, resim Paul Graham'ın aklından biraz farklı, ancak akıllıca bir hack ile her şey mümkün . Common Lisp'de hikaye kavramsal olarak biraz daha temiz olurdu. Ancak temel bilgiler benzerdir: Lisp Okuyucu, durum geçişleri gerçekleştirmenin ve sonunda bir "kabul durumuna" ulaşıp ulaşmadığını bildirmenin yanı sıra, karakterlerin temsil ettiği Lisp veri yapılarını tüküren bir durum makinesidir. Böylece karakterler 123sayı haline 123gelir vb. Şimdi önemli nokta gelir: bu durum makinesi kullanıcı kodu ile değiştirilebilir.. (Daha önce belirtildiği gibi, CL'nin durumunda bu tamamen doğrudur; Clojure için, bir hack (cesareti kırılmış ve pratikte kullanılmamış) gereklidir.

Dolayısıyla, bir Common Lisp programcısıysanız ve Clojure tarzı vektör değişmezleri fikrini beğenirseniz, okuyucuya bazı karakter dizilerine uygun şekilde tepki vermek için bir işlev takabilir - [veya #[muhtemelen - ve bunu eşleşmede biten bir vektör değişmezinin başlangıcı ]. Böyle bir işleve okuyucu makrosu denir ve tıpkı normal bir makro gibi, önceden kaydedilmiş okuyucu makroları tarafından etkinleştirilen eğlenceli gösterimle yazılan kod da dahil olmak üzere her türlü Lisp kodunu çalıştırabilir. Demek sizin için okuma zamanında tüm dil var.

Sarmalamak:

Aslında, şimdiye kadar gösterilen şey, normal Lisp işlevlerinin okuma zamanında veya derleme zamanında çalıştırılabileceğidir; Buradan okumanın ve derlemenin okuma, derleme veya çalıştırma sırasında nasıl mümkün olduğunu anlamak için atılması gereken adım, okuma ve derlemenin Lisp fonksiyonları tarafından gerçekleştirildiğini fark etmektir. Sırasıyla karakter akışlarından Lisp verilerini okumak için arayabilir readveya evalistediğiniz zaman Lisp kodunu derleyebilir ve çalıştırabilirsiniz. Her zaman oradaki bütün dil budur.

Lisp'in listenizdeki (3) noktasını karşılamasının, (4) numaralı noktayı karşılama şekli açısından ne kadar önemli olduğuna dikkat edin - Lisp tarafından sağlanan makroların özel çeşidi, büyük ölçüde normal Lisp verileriyle temsil edilen koda dayanır. bu (3) tarafından etkinleştirilen bir şeydir. Bu arada, kodun yalnızca "ağaç benzeri" yönü burada gerçekten çok önemlidir - XML ​​kullanarak yazılmış bir Lisp'e sahip olabilirsiniz.


4
Dikkat: "normal (derleyici) makro" diyerek, derleyici makrolarının "normal" makrolar olduğunu ima etmeye çok yakınsınız, ancak Common Lisp'de (en azından) "derleyici makrosu" çok özel ve farklı bir şeydir: lispworks. com / document / lw51 / CLHS / Body /…
Ken,

Ken: İyi yakaladın, teşekkürler! Bunu "normal makro" olarak değiştireceğim, ki bence kimsenin ayağa kalkması pek olası değil.
Michał Marczyk

Harika cevap. 5 dakika içinde, soruyu araştırırken / düşünürken saatlerde sahip olduğumdan daha fazlasını öğrendim. Teşekkürler.
Charlie Çiçek

Düzenleme: argh, sonradan gelen bir cümle yanlış anlaşıldı. Dilbilgisi için düzeltildi (düzenlememi kabul etmek için bir "eşe" ihtiyacım var).
Tatiana Racheva

S-ifadeleri ve XML aynı yapıları dikte edebilir ancak XML çok daha ayrıntılıdır ve bu nedenle sözdizimi olarak uygun değildir.
Sylwester

67

1) Yeni bir değişken kavramı. Lisp'te tüm değişkenler etkili bir şekilde işaretçilerdir. Değerler, değişkenlere değil türlere sahip olanlardır ve değişkenleri atamak veya bağlamak, işaret ettikleri şeyi değil, işaretçileri kopyalamak anlamına gelir.

(defun print-twice (it)
  (print it)
  (print it))

"it" bir değişkendir. HERHANGİ bir değere bağlanabilir. Değişkenle ilişkili herhangi bir sınırlama ve tür yoktur. Fonksiyonu çağırırsanız, argümanın kopyalanmasına gerek yoktur. Değişken bir işaretçiye benzer. Değişkene bağlı değere erişmenin bir yolu vardır. Bellek ayırmaya gerek yoktur . Fonksiyonu çağırdığımızda herhangi bir veri nesnesini iletebiliriz: herhangi bir boyut ve herhangi bir tür.

Veri nesnelerinin bir 'türü' vardır ve tüm veri nesneleri, 'türü' için sorgulanabilir.

(type-of "abc")  -> STRING

2) Bir sembol türü. Semboller, bir işaretçiyi karşılaştırarak eşitliği test edebilmeniz açısından dizelerden farklıdır.

Sembol, adı olan bir veri nesnesidir. Nesneyi bulmak için genellikle ad kullanılabilir:

|This is a Symbol|
this-is-also-a-symbol

(find-symbol "SIN")   ->  SIN

Semboller gerçek veri nesneleri olduğundan, aynı nesne olup olmadıklarını test edebiliriz:

(eq 'sin 'cos) -> NIL
(eq 'sin 'sin) -> T

Bu, örneğin sembollerle bir cümle yazmamızı sağlar:

(defvar *sentence* '(mary called tom to tell him the price of the book))

Şimdi cümledeki THE sayısını sayabiliriz:

(count 'the *sentence*) ->  2

Common Lisp'de sembollerin yalnızca bir adı yoktur, aynı zamanda bir değeri, işlevi, özellik listesi ve paketi de olabilir. Dolayısıyla, değişkenleri veya işlevleri adlandırmak için semboller kullanılabilir. Özellik listesi genellikle sembollere meta veri eklemek için kullanılır.

3) Sembol ağaçlarını kullanan kod için bir gösterim.

Lisp, kodu temsil etmek için temel veri yapılarını kullanır.

Liste (* 3 2) hem veri hem de kod olabilir:

(eval '(* 3 (+ 2 5))) -> 21

(length '(* 3 (+ 2 5))) -> 3

Ağaç:

CL-USER 8 > (sdraw '(* 3 (+ 2 5)))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 *        3       [*|*]--->[*|*]--->[*|*]--->NIL
                   |        |        |
                   v        v        v
                   +        2        5

4) Dilin tamamı her zaman mevcuttur. Okuma zamanı, derleme zamanı ve çalışma zamanı arasında gerçek bir ayrım yoktur. Kodu okurken derleyebilir veya çalıştırabilir, derleme sırasında kodu okuyabilir veya çalıştırabilir ve çalışma zamanında kodu okuyabilir veya derleyebilirsiniz.

Lisp, metinden veri ve kod okumak için OKU, kodu yüklemek için YÜKLE, kodu değerlendirmek için EVAL, kodu derlemek için DERLE ve metne veri ve kod yazmak için YAZDIR işlevlerini sağlar.

Bu işlevler her zaman mevcuttur. Bir yere gitmiyorlar. Herhangi bir programın parçası olabilirler. Bu, herhangi bir programın her zaman kodu okuyabileceği, yükleyebileceği, değerlendirebileceği veya yazdırabileceği anlamına gelir.

C veya Java gibi dillerde nasıl farklılar?

Bu diller, veri olarak semboller, kodlar veya kod olarak verilerin çalışma zamanı değerlendirmesi sağlamaz. C'deki veri nesneleri genellikle türsüzdür.

LISP aile dillerinden başka herhangi bir dil şu anda bu yapılardan herhangi birine sahip mi?

Birçok dilde bu yeteneklerden bazıları vardır.

Fark:

Lisp'de bu yetenekler, kullanımları kolay olacak şekilde dile göre tasarlanmıştır.


33

(1) ve (2) numaralı noktalar için, tarihsel olarak konuşuyor. Java'nın değişkenleri hemen hemen aynıdır, bu yüzden değerleri karşılaştırmak için .equals () 'ı çağırmanız gerekir.

(3) S-ifadelerinden bahsediyor. Lisp programları, Java ve C gibi geçici sözdizimine göre birçok avantaj sağlayan bu sözdiziminde yazılmıştır, örneğin makrolarda yinelenen desenleri C makrolarından veya C ++ şablonlarından çok daha temiz bir şekilde yakalamak ve kodu aynı çekirdek listesiyle değiştirmek gibi. veriler için kullandığınız işlemler.

(4) örneğin C almak: dil gerçekten iki farklı alt dildir: if () ve while () ve önişlemci gibi şeyler. Ön işlemciyi, kendinizi her zaman tekrarlamak zorunda kalmaktan kurtarmak veya # if / # ifdef ile kodu atlamak için kullanırsınız. Ancak her iki dil de oldukça ayrıdır ve derleme sırasında #if gibi while () kullanamazsınız.

C ++ bunu şablonlarla daha da kötüleştirir. Derleme zamanında kod üretmenin bir yolunu sağlayan ve uzman olmayanların kafalarını toplamaları son derece zor olan şablon meta programlamayla ilgili birkaç referansı inceleyin. Buna ek olarak, bu, derleyicinin birinci sınıf destek sağlayamayacağı, şablonlar ve makrolar kullanan bir dizi hile ve hiledir - basit bir sözdizimi hatası yaparsanız, derleyici size net bir hata mesajı veremez.

Pekala, Lisp ile tüm bunlara tek bir dilde sahipsiniz. İlk gününüzde öğrendiğinizde, çalışma zamanında kod oluşturmak için aynı şeyleri kullanırsınız. Bu, meta programlamanın önemsiz olduğunu öne sürmek anlamına gelmez, ancak birinci sınıf dil ve derleyici desteği ile kesinlikle daha basittir.


7
Ayrıca, bu güç (ve basitlik) artık 50 yaşın üzerindedir ve acemi bir programcının asgari rehberlikle başarabileceği ve dilin temellerini öğrenebileceği kadar kolay uygulanır. İyi bir başlangıç ​​projesi olarak Java, C, Python, Perl, Haskell gibi benzer bir iddiayı duymazsınız!
Matt Curtis

9
Java değişkenlerinin Lisp sembolleri gibi olduğunu sanmıyorum. Java'da bir sembol için gösterim yoktur ve bir değişkenle yapabileceğiniz tek şey onun değer hücresini almaktır. Dizeler dahil edilebilir ancak bunlar tipik isimler değildir, bu yüzden alıntılanıp değerlendirilemeyecekleri, geçilip geçilemeyecekleri vb. Hakkında konuşmak bile mantıklı değildir.
Ken

2
40 yaşın üzerindeyken daha doğru olabilir :), @Ken: Sanırım 1) java'daki ilkel olmayan değişkenlerin aslında lisp'e benzeyen ve 2) javadaki dahili dizelerin lisp'deki sembollere benzediğini kastediyor - Tabii ki, dediğin gibi, Java'da stajyer diziler / kodlardan alıntı yapamaz veya bunları değerlendiremezsin, bu yüzden hala oldukça farklılar.

3
@Dan - İlk uygulamanın ne zaman bir araya getirildiğinden emin değilim, ancak sembolik hesaplama hakkındaki ilk McCarthy makalesi 1960'da yayınlandı.
Inaimathi

Java, Foo.class / foo.getClass () biçimindeki "semboller" için kısmi / düzensiz desteğe sahiptir - yani bir tür türü Sınıf <Foo> nesnesi, enum değerleri gibi biraz benzerdir. bir derece. Ama bir Lisp sembolünün çok az gölgesi.
BRPocock

-4

(1) ve (2) noktaları da Python'a uyacaktır. Basit bir örnek alarak "a = str (82.4)" yorumlayıcı ilk olarak 82.4 değerine sahip bir kayan nokta nesnesi oluşturur. Daha sonra bir dize oluşturucusunu çağırır ve bu daha sonra '82 .4 'değerine sahip bir dize döndürür. Sol taraftaki 'a' sadece bu dizgi nesnesi için bir etikettir. Orijinal kayan nokta nesnesi, kendisine daha fazla başvuru olmadığı için çöp toplandı.

Scheme'de her şey benzer şekilde bir nesne olarak ele alınır. Common Lisp hakkında emin değilim. C / C ++ kavramları açısından düşünmekten kaçınmaya çalışırdım. Lisps'in güzel sadeliğine kafamı sokmaya çalışırken beni yığınlar halinde yavaşlattılar.

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.