Menurut artikel ini , baris kode Lisp berikut mencetak "Hello world" ke output standar.
(format t "hello, world")
Lisp, yang merupakan bahasa homoikonik , dapat memperlakukan kode sebagai data dengan cara ini:
Sekarang bayangkan kita menulis makro berikut:
(defmacro backwards (expr) (reverse expr))
mundur adalah nama makro, yang mengambil ekspresi (direpresentasikan sebagai daftar), dan membalikkannya. Inilah "Halo, dunia" lagi, kali ini menggunakan makro:
(backwards ("hello, world" t format))
Ketika kompiler Lisp melihat baris kode itu, ia melihat atom pertama dalam daftar (
backwards
), dan pemberitahuan bahwa itu menamai makro. Ini melewati daftar yang tidak dievaluasi("hello, world" t format)
ke makro, yang mengatur ulang daftar(format t "hello, world")
. Daftar yang dihasilkan menggantikan ekspresi makro, dan itulah yang akan dievaluasi saat dijalankan. Lingkungan Lisp akan melihat bahwa atom pertamanya (format
) adalah fungsi, dan mengevaluasinya, meneruskannya dengan argumen lainnya.
Dalam Lisp mencapai tugas ini mudah (koreksi saya jika saya salah) karena kode diimplementasikan sebagai daftar ( s-ekspresi ?).
Sekarang lihat cuplikan OCaml ini (yang bukan bahasa homoikonik):
let print () =
let message = "Hello world" in
print_endline message
;;
Bayangkan Anda ingin menambahkan homoiconicity ke OCaml, yang menggunakan sintaks yang jauh lebih kompleks dibandingkan dengan Lisp. Bagaimana Anda akan melakukannya? Apakah bahasa tersebut harus memiliki sintaksis yang sangat mudah untuk mencapai homoiconicity?
EDIT : dari topik ini saya menemukan cara lain untuk mencapai homoiconicity yang berbeda dari Lisp: yang diterapkan dalam bahasa io . Mungkin sebagian menjawab pertanyaan ini.
Di sini, mari kita mulai dengan blok sederhana:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
Oke, jadi bloknya berfungsi. Blok plus menambahkan dua angka.
Sekarang mari kita lakukan introspeksi pada anak kecil ini.
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 ==> +
Cetakan dingin suci panas. Anda tidak hanya bisa mendapatkan nama-nama params blok. Dan Anda tidak hanya dapat memperoleh string dari kode sumber lengkap blok. Anda dapat menyelinap masuk ke kode dan menelusuri pesan di dalamnya. Dan yang paling menakjubkan: sangat mudah dan alami. Sesuai dengan pencarian Io. Cermin Ruby tidak bisa melihat semua itu.
Tapi, whoa whoa, hei sekarang, jangan menyentuh tombol itu.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1
Jawaban:
Anda dapat membuat bahasa apa saja menjadi homoikonik. Pada dasarnya Anda melakukan ini dengan 'mirroring' bahasa (artinya untuk setiap konstruktor bahasa Anda menambahkan representasi yang sesuai dari konstruktor itu sebagai data, pikirkan AST). Anda juga perlu menambahkan beberapa operasi tambahan seperti kutipan dan tanda kutip. Itu kurang lebih itu.
Lisp memiliki itu sejak awal karena sintaksinya yang mudah, tetapi keluarga MetaML bahasa W. Taha menunjukkan bahwa itu mungkin dilakukan untuk bahasa apa pun.
Seluruh proses diuraikan dalam Pemodelan meta-pemrograman generatif yang homogen . Pengantar yang lebih ringan untuk bahan yang sama ada di sini .
sumber
Compiler Ocaml ditulis dalam Ocaml itu sendiri, jadi tentu saja ada cara untuk memanipulasi Ocaml AST di Ocaml.
Orang mungkin membayangkan menambahkan tipe built-in
ocaml_syntax
ke bahasa, dan memiliki fungsidefmacro
built-in, yang mengambil input tipe, katakanSekarang apa jenis dari
defmacro
? Nah itu benar-benar tergantung pada input, karena meskipunf
adalah fungsi identitas, jenis potongan kode yang dihasilkan tergantung pada potongan sintaks yang dilewatkan.Masalah ini tidak muncul di lisp, karena bahasa diketik secara dinamis, dan tidak ada jenis yang harus dianggap berasal dari makro itu sendiri pada waktu kompilasi. Salah satu solusinya adalah memiliki
yang akan memungkinkan makro untuk digunakan dalam konteks apa pun . Tapi ini tidak aman, tentu saja, itu akan memungkinkan
bool
untuk digunakan sebagai penggantistring
, crash program pada saat run-time.Satu-satunya solusi berprinsip dalam bahasa yang diketik secara statis adalah memiliki tipe dependen di mana tipe hasil
defmacro
akan bergantung pada input. Hal-hal menjadi sangat rumit pada titik ini, dan saya akan mulai dengan menunjukkan Anda pada disertasi yang bagus oleh David Raymond Christiansen.Kesimpulannya: memiliki sintaks yang rumit bukanlah masalah, karena ada banyak cara untuk mewakili sintaks di dalam bahasa, dan mungkin menggunakan meta-programing seperti
quote
operasi untuk menanamkan sintaks "sederhana" ke dalam internalocaml_syntax
.Masalahnya adalah membuat ini diketik dengan baik, khususnya memiliki mekanisme makro run-time yang tidak memungkinkan untuk kesalahan ketik.
Memiliki waktu kompilasi mekanisme untuk macro dalam bahasa seperti Ocaml mungkin tentu saja, lihat misalnya MetaOcaml .
Juga mungkin berguna: Jane street pada program-meta di Ocaml
sumber
Sebagai contoh, pertimbangkan F # (berdasarkan OCaml). F # tidak sepenuhnya homoikonik, tetapi mendukung mendapatkan kode fungsi sebagai AST dalam kondisi tertentu.
Dalam F #, Anda
print
akan direpresentasikan sebagaiExpr
yang dicetak sebagai:Untuk menyoroti struktur dengan lebih baik, berikut ini cara alternatif untuk membuat hal yang sama
Expr
:sumber
eval(<string>)
fungsi ini? ( Menurut banyak sumber, memiliki fungsi eval berbeda dari memiliki homoiconicity - apakah itu alasan mengapa Anda mengatakan F # tidak sepenuhnya homoiconic?)print
dengan[<ReflectedDefinition>]
atribut.)