Bir dil homoikonik nasıl yapılır


16

Bu makaleye göre , aşağıdaki Lisp kodu satırı "Merhaba dünya" yı standart çıktıya yazdırır.

(format t "hello, world")

Homikonik bir dil olan Lisp, kodu veri olarak şu şekilde ele alabilir:

Şimdi aşağıdaki makroyu yazdığımızı hayal edin:

(defmacro backwards (expr) (reverse expr))

geriye doğru, bir ifadeyi (liste olarak temsil edilir) alan ve tersine çeviren makronun adıdır. İşte yine "Merhaba, dünya", bu sefer makroyu kullanarak:

(backwards ("hello, world" t format))

Lisp derleyicisi bu kod satırını gördüğünde, listedeki ( backwards) ilk atoma bakar ve makro adını verdiğini fark eder. Değerlendirilmemiş listeyi listeye ("hello, world" t format)yeniden düzenleyen makroya geçirir (format t "hello, world"). Sonuçta ortaya çıkan liste makro ifadesinin yerini alır ve çalışma zamanında değerlendirilecek olan budur. Lisp ortamı, ilk atomunun ( format) bir işlev olduğunu görecek ve onu değerlendirecek ve argümanların geri kalanını geçirecektir .

Lisp'te bu görevi gerçekleştirmek kolaydır (eğer yanlışsam beni düzelt) çünkü kod liste ( s-ifadeleri ?) Olarak uygulanır.

Şimdi bu OCaml (homoikonik bir dil değildir) snippet'ine bir göz atın:

let print () =
    let message = "Hello world" in
    print_endline message
;;

Lisp ile karşılaştırıldığında çok daha karmaşık bir sözdizimi kullanan OCaml'a homoiklik eklemek istediğinizi düşünün. Bunu nasıl yaptın? Homikoniklik sağlamak için dilin özellikle kolay bir sözdizimine sahip olması gerekir mi?

DÜZENLEME : Bu konudan Lisp'lerden farklı bir homoiklik elde etmenin başka bir yolunu buldum : io dilinde uygulanan . Bu soruya kısmen cevap verebilir.

Burada basit bir blokla başlayalım:

Io> plus := block(a, b, a + b)
==> method(a, b, 
        a + b
    )
Io> plus call(2, 3)
==> 5

Tamam, blok çalışıyor. Artı bloğu iki sayı ekledi.

Şimdi bu küçük adama biraz içgözlem yapalım.

Io> plus argumentNames
==> list("a", "b")
Io> plus code
==> block(a, b, a +(b))
Io> plus message name
==> a
Io> plus message next
==> +(b)
Io> plus message next name
==> +

Sıcak kutsal soğuk kalıp. Sadece blok parametrelerinin isimlerini elde etmekle kalmaz. Ve sadece bloğun tam kaynak kodunun bir dizesini elde etmekle kalmaz. Koda gizlice girip içindeki mesajları değiştirebilirsiniz. Ve en şaşırtıcısı: çok kolay ve doğal. Io'nun arayışına sadık. Ruby'nin aynası bunların hiçbirini göremiyor.

Ama, whoa whoa, hey şimdi, o kadrana dokunma.

Io> plus message next setName("-")
==> -(b)
Io> plus
==> method(a, b, 
        a - b
    )
Io> plus call(2, 3)
==> -1


1
Bergi Scala'nın makrolara yeni bir yaklaşımı var: scala.meta .
Martin Berger

Her zaman homoikonisite abartılıyor. Yeterince güçlü bir dilde her zaman dilin yapısını yansıtan bir ağaç yapısı tanımlayabilirsiniz ve kaynak dilin (ve / veya derlenmiş bir formun) gerektiğinde ve dilden çevrilmesi için yardımcı program işlevleri yazılabilir. Evet, LISP'lerde biraz daha kolay, ancak (a) programlama çalışmasının büyük çoğunluğunun metaprogramlama olmaması ve (b) LISP'nin bunu mümkün kılmak için dil netliğini feda ettiği göz önüne alındığında, denemeye değdiğini sanmıyorum.
Periata Breatta

@PeriataBreatta Haklısınız, ancak MP'nin en önemli avantajı, MP'nin çalışma zamanı cezası olmadan soyutlamalara olanak vermesidir . Böylece MP, dil karmaşıklığını artırma pahasına da olsa, soyutlama ve performans arasındaki gerilimi giderir. Buna değer mi? Ben aslında derdim tüm önemli PL MP uzantılarına sahip çalışma programcılar bir çok MP kullanışlı sunmaktadır ödünleşimleri bulduğunu gösterir.
Martin Berger

Yanıtlar:


10

Herhangi bir dili homoikonik yapabilirsiniz. Aslında bunu dili 'yansıtarak' yaparsınız (herhangi bir dil kurucusu için bu kurucunun veri olarak karşılık gelen bir temsilini eklersiniz, AST'yi düşünün). Ayrıca, alıntılama ve alıntıdan çıkarma gibi birkaç ek işlem eklemeniz gerekir. Bu aşağı yukarı.

Lisp, kolay sözdizimi nedeniyle bunu erkenden yaptı, ancak W. Taha'nın MetaML dil ailesi, herhangi bir dil için yapılabileceğini gösterdi.

Tüm süreç, homojen üretken meta-programlamanın modellenmesinde açıklanmaktadır . Aynı malzemeye daha hafif bir giriş burada .


1
Yanlışsam düzelt. "yansıtma" sorunun ikinci kısmı ile ilgilidir (io lang'de homoikonisite), değil mi?
incud

@Ignus Yorumunu tam olarak anladığımdan emin değilim. Homikonikliğin amacı, kodun veri olarak işlenmesini sağlamaktır. Bu, herhangi bir kod biçiminin veri olarak bir temsili olması gerektiği anlamına gelir. Bunu yapmanın birkaç yolu vardır (örneğin, hafif modüler evreleme yaklaşımı ile yapılan koddan verileri ayırmak için türleri kullanan ASTs yarı-tırnakları), ancak bunların hepsi bir şekilde dil sözdiziminin ikiye katlanmasını / aynalanmasını gerektirir.
Martin Berger

@Ignus'un MetaOCaml'a bakmaktan fayda sağlayacağını varsayıyorum? "Homikonik" olmak sadece o zaman alıntı yapmak anlamına mı geliyor? MetaML ve MetaOCaml gibi çok aşamalı dillerin daha ileri gittiğini varsayıyorum?
Steven Shaw

1
@StevenShaw MetaOCaml çok ilginç, özellikle Oleg'in yeni BER MetaOCaml'ı . Bununla birlikte, sadece çalışma zamanı meta-programlama gerçekleştirmesi ve kodu sadece AST'ler kadar etkileyici olmayan yarı tırnaklarla temsil etmesi biraz kısıtlıdır.
Martin Berger

7

Ocaml derleyici böylece, ocaml kendisi yazılır kesinlikle ocaml içinde Ocaml FBDTÖ işlemek için bir yolu yoktur.

ocaml_syntaxDile yerleşik bir tür eklediğinizi ve tür defmacrogirişi olan yerleşik bir işleve sahip olduğunu düşünebilirsiniz.

f : ocaml_syntax -> ocaml_syntax

Şimdi ne tip bir defmacro? Eh, bu girişe bağlıdır f, kimlik işlevi olsa bile , ortaya çıkan kod parçasının türü geçirilen sözdizimine bağlıdır.

Lisp'de bu sorun ortaya çıkmaz, çünkü dil dinamik olarak yazılır ve derleme zamanında hiçbir türün makronun kendisine atfedilmesi gerekmez. Bir çözüm sahip olmak

defmacro : (ocaml_syntax -> ocaml_syntax) -> 'a

bu da makronun herhangi bir bağlamda kullanılmasına izin verir . Ancak bu güvenli değildir, elbette, boola'nın yerine stringprogramın çalışma zamanında çökmesine izin verir .

Statik olarak yazılan bir dilde tek ilkeli çözüm , sonuç türünün girdiye bağlı olacağı bağımlı türlere sahip olmak defmacroolacaktır. Bu noktada işler oldukça karmaşıklaşıyor ve sizi David Raymond Christiansen'in güzel tezine yönlendirerek başlayacağım .

Sonuç olarak: sözdizimini dil içinde temsil etmenin pek çok yolu olduğundan ve muhtemelen quote"basit" sözdizimini dâhili içine gömmek için bir işlem gibi meta programlamayı kullanmak gibi karmaşık bir sözdizimine sahip olmak bir sorun değildir ocaml_syntax.

Sorun, özellikle tür hatalarına izin vermeyen bir çalışma zamanı makro mekanizmasına sahip olmak, bu tür yazılan yapmaktır.

Ocaml gibi bir dilde makrolar için bir derleme zamanı mekanizmasına sahip olmak elbette mümkündür, bkz. Örneğin MetaOcaml .

Ayrıca yararlı: Ocaml'de meta-programlama üzerine Jane Caddesi


2
MetaOCaml, derleme zamanı meta-programlama değil, çalışma zamanı-meta-programlama özelliğine sahiptir. Ayrıca MetaOCaml'ın yazım sisteminin bağımlı türleri yoktur. (MetaOCaml'ın da tip-temelsiz olduğu bulundu!) Şablon Haskell'in ilginç bir ara yaklaşımı var: her aşama tipte güvenlidir, ancak yeni bir aşamaya girerken, tip kontrolünü tekrar yapmalıyız. Bu benim deneyimimde pratikte gerçekten işe yarıyor ve son (çalışma zamanı) aşamada tip güvenliğinin faydalarını kaybetmiyorsunuz.
Martin Berger

@cody OCaml'de Uzatma Noktaları ile de metaprogramlama yapmak mümkün , değil mi?
20'de incud

@Ignus Korkarım Uzantı Noktaları hakkında fazla bir şey bilmiyorum, ancak Jane Street bloguna bağlantıda referans veriyorum.
cody

1
C derleyicim C ile yazılmış, ancak bu AST C manipüle edebileceğiniz anlamına gelmez ...
BlueRaja - Danny Pflughoeft

2
@immibis: Açıkçası, ama kastettiği buysa, bu ifade hem boş hem de soru ile ilgisiz ...
BlueRaja - Danny Pflughoeft

1

Örnek olarak F # 'ı (OCaml temelli) ele alalım. F # tam olarak homoikonik değildir, ancak belirli durumlarda bir fonksiyonun kodunu AST olarak almayı destekler.

F #, senin printbir ile temsil edilir Exprolarak basılmış olması:

Let (message, Value ("Hello world"), Call (None, print_endline, [message]))

Yapıyı daha iyi vurgulamak için, bunu nasıl oluşturabileceğinizin alternatif bir yolu Expr:

let messageVar = Var("message", typeof<string>)
let expr = Expr.Let(messageVar,
                    Expr.Value("Hello world"),
                    Expr.Call(print_endline_method, [Expr.Var(messageVar)]))

Ben bunu anlamadım. Yani F #, bir ifadenin AST'sini "oluşturmanıza" ve ardından onu çalıştırmanıza izin veriyor mu? Öyleyse, eval(<string>)işlevi kullanmanıza izin veren diller arasındaki fark nedir? ( Birçok kaynağa göre, eval fonksiyonuna sahip olmak homoikonikliğe sahip olmaktan farklıdır - F #'ın tamamen homoik olmadığını söylemenizin sebebi bu mu?)
19'da incud

@Ignus AST'yi kendiniz oluşturabilir veya derleyicinin yapmasına izin verebilirsiniz. Homoiconicity " dildeki tüm kodlara erişilmesine ve veri olarak dönüştürülmesine izin verir" . F # 'da, bazı koda veri olarak erişebilirsiniz . (Örneğin, işaretine ihtiyaç printolan [<ReflectedDefinition>]öznitelik.)
svick
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.