Saya hanya memiliki pengetahuan terbatas tentang Lisp (mencoba belajar sedikit di waktu luang saya) tetapi sejauh yang saya mengerti Lisp macro memungkinkan untuk memperkenalkan konstruksi bahasa baru dan sintaksis dengan menggambarkannya dalam Lisp itu sendiri. Ini berarti bahwa konstruk baru dapat ditambahkan sebagai pustaka, tanpa mengubah kompiler / interpreter Lisp.
Pendekatan ini sangat berbeda dengan bahasa pemrograman lainnya. Misalnya, jika saya ingin memperluas Pascal dengan jenis loop baru atau idiom tertentu saya harus memperluas sintaks dan semantik bahasa dan kemudian mengimplementasikan fitur baru itu di kompiler.
Apakah ada bahasa pemrograman lain di luar keluarga Lisp (yaitu selain Common Lisp, Scheme, Clojure (?), Racket (?), Dll) yang menawarkan kemungkinan serupa untuk memperluas bahasa dalam bahasa itu sendiri?
EDIT
Harap, hindari diskusi yang panjang dan spesifik dalam jawaban Anda. Alih-alih daftar panjang bahasa pemrograman yang dapat diperluas dengan cara tertentu, saya ingin memahami dari sudut pandang konseptual apa yang spesifik untuk makro Lisp sebagai mekanisme ekstensi, dan bahasa pemrograman non-Lisp yang menawarkan beberapa konsep itu dekat dengan mereka.
sumber
Jawaban:
Scala memungkinkan ini juga (sebenarnya secara sadar dirancang untuk mendukung definisi konstruksi bahasa baru dan bahkan menyelesaikan DSL).
Terlepas dari fungsi tingkat tinggi, lambdas dan kari, yang umum dalam bahasa fungsional, ada beberapa fitur bahasa khusus di sini untuk memungkinkan ini *:
a.and(b)
setara dengana and b
dalam bentuk infiksuntuk panggilan fungsi parameter tunggal, Anda dapat menggunakan kurung keriting alih-alih kawat gigi normal - ini (bersama dengan kari) memungkinkan Anda untuk menulis hal-hal seperti
dimana
withPrintWriter
adalah metode biasa dengan dua daftar parameter, keduanya berisi satu parametermyAssert(() => x > 3)
dalam bentuk yang lebih pendekmyAssert(x > 3)
Penciptaan contoh DSL dibahas secara rinci dalam Bab 11. Bahasa Domain-Khusus di Scala dari buku gratis Pemrograman Scala .
* Saya tidak bermaksud ini unik untuk Scala, tetapi setidaknya mereka sepertinya tidak terlalu umum. Saya bukan ahli dalam bahasa fungsional.
sumber
Perl memungkinkan untuk pra-pemrosesan bahasanya. Meskipun ini tidak sering digunakan untuk mengubah sintaksis secara radikal dalam bahasa, hal ini dapat dilihat pada beberapa ... modul ganjil:
Ada juga modul yang memungkinkan perl untuk menjalankan kode yang sepertinya ditulis dengan Python.
Pendekatan yang lebih modern untuk perl ini adalah dengan menggunakan Filter :: Simple (salah satu modul inti di perl5).
Perhatikan bahwa semua contoh itu melibatkan Damian Conway yang telah disebut sebagai "Dokter Gila Perl". Masih kemampuan yang luar biasa kuat dalam perl untuk memutar bahasa seperti yang diinginkan.
Lebih banyak dokumentasi untuk ini dan alternatif lain ada di perlfilter .
sumber
Haskell
Haskell memiliki "Templat Haskell" serta "Kuasiquotation":
http://www.haskell.org/haskellwiki/Template_Haskell
http://www.haskell.org/haskellwiki/Quasiquotation
Fitur-fitur ini memungkinkan pengguna untuk secara dramatis menambah sintaks bahasa di luar cara normal. Ini diselesaikan pada waktu kompilasi juga, yang saya pikir adalah suatu keharusan besar (untuk bahasa yang dikompilasi setidaknya) [1].
Saya telah menggunakan quasiquotation di Haskell sekali sebelumnya untuk membuat pencocokan pola lanjutan di atas bahasa C-like:
[1] Lain-lain yang memenuhi syarat sebagai ekstensi sintaks:,
runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"
yang tentu saja merupakan omong kosong.sumber
forM
).Tcl memiliki sejarah panjang dalam mendukung sintaks yang dapat dikembangkan. Sebagai contoh, inilah implementasi dari loop yang mengulangi tiga variabel (sampai berhenti) di atas kardinal, kotak dan kubus mereka:
Itu kemudian akan digunakan seperti ini:
Jenis teknik ini digunakan secara luas dalam pemrograman Tcl, dan kunci untuk melakukannya dengan benar adalah
upvar
danuplevel
perintah (upvar
mengikat variabel bernama dalam lingkup lain ke variabel lokal, danuplevel
menjalankan skrip di lingkup lain: dalam kedua kasus,1
menunjukkan bahwa ruang lingkup yang dimaksud adalah penelepon). Ini juga banyak digunakan dalam kode yang dipasangkan dengan basis data (menjalankan beberapa kode untuk setiap baris dalam set hasil), dalam Tk untuk GUI (untuk melakukan pengikatan callback ke acara), dll.Namun, itu hanya sebagian kecil dari apa yang dilakukan. Bahasa yang disematkan bahkan tidak perlu Tcl; itu bisa berupa apa saja (selama itu menyeimbangkan kawat gigi - hal-hal menjadi mengerikan secara sintaksis jika itu tidak benar - yang merupakan sebagian besar program) dan Tcl hanya dapat mengirim ke bahasa asing yang tertanam seperlunya. Contoh untuk melakukan ini termasuk menanamkan C untuk mengimplementasikan perintah Tcl dan yang setara dengan Fortran. (Dapat diperdebatkan, semua perintah bawaan Tcl dilakukan dengan cara ini dalam arti tertentu, dalam arti mereka hanya pustaka standar dan bukan bahasa itu sendiri.)
sumber
Ini sebagian pertanyaan semantik. Ide dasar Lisp adalah bahwa program ini adalah data yang dapat dimanipulasi sendiri. Bahasa yang umum digunakan dalam keluarga Lisp, seperti Skema, jangan biarkan Anda menambahkan sintaks baru dalam pengertian parser; itu semua hanya tanda kurung yang dibatasi ruang. Hanya saja karena sintaksis inti tidak begitu banyak, Anda dapat membuat hampir semua konstruksi semantik darinya. Scala (dibahas di bawah) serupa: aturan nama-variabel sangat liberal sehingga Anda dapat dengan mudah membuat DSL yang bagus darinya (sambil tetap berada dalam aturan sintaksis inti yang sama).
Bahasa-bahasa ini, sementara mereka tidak benar-benar membiarkan Anda mendefinisikan sintaks baru dalam arti filter Perl, memiliki inti yang cukup fleksibel yang dapat Anda gunakan untuk membangun DSL dan menambahkan konstruksi bahasa.
Fitur umum yang penting adalah bahwa mereka memungkinkan Anda menentukan konstruksi bahasa yang berfungsi serta yang built-in, menggunakan fitur-fitur yang diekspos oleh bahasa. Tingkat dukungan untuk fitur ini bervariasi:
sin()
,round()
, dll, tanpa cara untuk mengimplementasikan Anda sendiri.static_cast<target_type>(input)
,dynamic_cast<>()
,const_cast<>()
,reinterpret_cast<>()
) dapat ditiru menggunakan fungsi template, yang menggunakan Meningkatkan untuklexical_cast<>()
,polymorphic_cast<>()
,any_cast<>()
, ....for(;;){}
,while(){}
,if(){}else{}
,do{}while()
,synchronized(){}
,strictfp{}
) dan tidak membiarkan Anda menentukan sendiri. Scala sebagai gantinya mendefinisikan sintaksis abstrak yang memungkinkan Anda memanggil fungsi menggunakan sintaksis seperti struktur kontrol yang nyaman, dan pustaka menggunakan ini untuk secara efektif mendefinisikan struktur kontrol baru (misalnyareact{}
di perpustakaan aktor).Juga, Anda mungkin melihat fungsionalitas sintaksis khusus Mathematica dalam paket Notation . (Secara teknis itu ada di keluarga Lisp, tetapi memiliki beberapa fitur ekstensibilitas yang dilakukan secara berbeda, serta ekstensibilitas Lisp yang biasa.)
sumber
(defmacro ...)
. Sebenarnya saya saat ini memindahkan bahasa ini ke Racket, hanya untuk bersenang-senang. Tapi, saya setuju bahwa itu adalah sesuatu yang tidak terlalu berguna, karena sintaks ekspresi-S lebih dari cukup untuk sebagian besar semantik yang mungkin berguna.(define-macro ...)
setara, yang, pada gilirannya, dapat menggunakan segala jenis penguraian secara internal.Rebol terdengar hampir seperti apa yang Anda gambarkan, tetapi sedikit menyamping.
Daripada mendefinisikan sintaksis spesifik, semua yang ada di Rebol adalah panggilan fungsi - tidak ada kata kunci. (Ya, Anda dapat mendefinisikan ulang
if
danwhile
jika Anda benar-benar ingin). Misalnya, ini adalahif
pernyataan:if
adalah fungsi yang mengambil 2 argumen: kondisi dan blok. Jika kondisinya benar, blok dievaluasi. Kedengarannya seperti kebanyakan bahasa, bukan? Yah, blok adalah struktur data, tidak terbatas pada kode - ini adalah blok blok, misalnya, dan contoh cepat dari fleksibilitas "kode adalah data":Selama Anda dapat tetap berpegang pada aturan sintaksis, memperluas bahasa ini, sebagian besar, tidak lebih dari mendefinisikan fungsi-fungsi baru. Beberapa pengguna telah mendukung fitur Rebol 3 ke dalam Rebol 2, misalnya.
sumber
Ruby memiliki sintaks yang cukup fleksibel, saya pikir itu adalah cara "memperluas bahasa di dalam bahasa itu sendiri".
Contohnya adalah rake . Itu ditulis dalam Ruby, itu adalah Ruby, tetapi sepertinya make .
Untuk memeriksa beberapa kemungkinan, Anda dapat mencari kata kunci Ruby dan metaprogramming .
sumber
Memperluas sintaks seperti yang Anda bicarakan memungkinkan Anda membuat bahasa khusus domain. Jadi mungkin cara yang paling berguna untuk mengulangi pertanyaan Anda adalah, bahasa apa yang memiliki dukungan yang baik untuk bahasa khusus domain?
Ruby memiliki sintaks yang sangat fleksibel, dan banyak DSL telah berkembang di sana, seperti rake. Groovy termasuk banyak kebaikan itu. Ini juga mencakup transformasi AST, yang lebih langsung analog dengan makro Lisp.
R, bahasa untuk komputasi statistik, memungkinkan fungsi untuk membuat argumen mereka tidak dievaluasi. Ini menggunakan ini untuk membuat DSL untuk menentukan rumus regresi. Sebagai contoh:
berarti "paskan garis bentuk k0 + k1 * a + k2 * b dengan nilai dalam y."
berarti "paskan garis bentuk k0 + k1 * a + k2 * b + k3 * a * b dengan nilai dalam y."
Dan seterusnya.
sumber
Converge adalah satu lagi bahasa pemograman non-lispy. Dan, sampai batas tertentu, C ++ memenuhi syarat juga.
Boleh dibilang, MetaOCaml cukup jauh dari Lisp. Untuk gaya ekstensi sintaks yang sama sekali berbeda, namun cukup kuat, lihat CamlP4 .
Nemerle adalah bahasa lain yang bisa dikembangkan dengan pemrograman ala Lisp, meskipun lebih dekat ke bahasa seperti Scala.
Dan, Scala sendiri akan segera menjadi bahasa seperti itu juga.
Sunting: Saya lupa tentang contoh paling menarik - JetBrains MPS . Ini tidak hanya sangat jauh dari apa pun Lispish, itu bahkan merupakan sistem pemrograman non-tekstual, dengan editor yang beroperasi langsung pada tingkat AST.
Sunting2: Untuk menjawab pertanyaan yang diperbarui - tidak ada yang unik dan luar biasa di makro Lisp. Secara teori, bahasa apa pun dapat menyediakan mekanisme seperti itu (saya bahkan melakukannya dengan C sederhana). Yang Anda butuhkan hanyalah akses ke AST Anda dan kemampuan untuk mengeksekusi kode dalam waktu kompilasi. Beberapa refleksi mungkin membantu (menanyakan tentang jenis, definisi yang ada, dll.).
sumber
Prolog memungkinkan mendefinisikan operator baru yang diterjemahkan ke istilah majemuk dengan nama yang sama. Misalnya, ini mendefinisikan
has_cat
operator dan mendefinisikannya sebagai predikat untuk memeriksa apakah daftar berisi atomcat
:The
xf
berarti bahwahas_cat
adalah operator postfix; menggunakanfx
akan menjadikannya operator awalan, danxfx
membuatnya menjadi operator infiks, mengambil dua argumen. Periksa tautan ini untuk detail lebih lanjut tentang mendefinisikan operator di Prolog.sumber
TeX benar-benar hilang dari daftar. Anda semua tahu itu, kan? Itu terlihat seperti ini:
... kecuali Anda dapat mendefinisikan ulang sintaksis tanpa batasan. Setiap token (!) Dalam bahasa dapat diberi arti baru. ConTeXt adalah paket makro yang telah menggantikan kurung kurawal dengan kurung kurawal:
Paket makro yang lebih umum, LaTeX juga mendefinisikan kembali bahasa untuk tujuannya, misalnya menambahkan
\begin{environment}…\end{environment}
sintaks.Tapi itu tidak berhenti di situ. Secara teknis, Anda bisa mendefinisikan ulang token untuk menguraikan berikut:
Ya, sangat mungkin. Beberapa paket menggunakan ini untuk mendefinisikan bahasa spesifik domain kecil. Misalnya, paket TikZ mendefinisikan sintaksis ringkas untuk gambar teknis, yang memungkinkan berikut ini:
Selanjutnya, TeX adalah Turing lengkap sehingga Anda benar-benar dapat melakukan semuanya dengan itu. Saya belum pernah melihat ini digunakan untuk potensi penuh karena itu akan sangat tidak berguna dan sangat berbelit-belit tetapi sangat mungkin untuk membuat kode berikut dapat diuraikan hanya dengan mendefinisikan kembali token (tetapi ini mungkin akan pergi ke batas fisik parser, karena cara itu dibangun):
sumber
Boo memungkinkan Anda menyesuaikan bahasa dengan berat pada waktu kompilasi melalui makro sintaksis.
Boo memiliki "pipa penyusun extensible". Itu berarti kompiler dapat memanggil kode Anda untuk melakukan transformasi AST di titik mana pun selama pipa kompiler. Seperti yang Anda ketahui, hal-hal seperti Java's Generics atau C #'s Linq hanyalah transformasi sintaksis pada waktu kompilasi, jadi ini cukup kuat.
Dibandingkan dengan Lisp, keuntungan utamanya adalah ini bekerja dengan semua jenis sintaksis. Boo menggunakan sintaks yang diilhami oleh Python, tetapi Anda mungkin bisa menulis kompilator yang dapat diperluas dengan sintaks C atau Pascal. Dan karena makro dievaluasi pada waktu kompilasi, tidak ada penalti kinerja.
Kerugian, dibandingkan dengan Lisp, adalah:
Misalnya, ini adalah bagaimana Anda dapat menerapkan struktur kontrol baru:
pemakaian:
yang kemudian diterjemahkan, pada waktu kompilasi, ke sesuatu seperti:
(Sayangnya, dokumentasi online Boo selalu ketinggalan zaman dan bahkan tidak mencakup hal-hal canggih seperti ini. Dokumentasi terbaik untuk bahasa yang saya tahu adalah buku ini: http://www.manning.com/rahien/ )
sumber
Evaluasi Mathematica didasarkan pada pencocokan pola dan penggantian. Itu memungkinkan Anda untuk membuat struktur kontrol Anda sendiri, mengubah struktur kontrol yang ada atau mengubah cara ekspresi dievaluasi. Misalnya, Anda dapat menerapkan "logika fuzzy" seperti ini (sedikit disederhanakan):
Ini mengesampingkan evaluasi untuk operator logis yang sudah ditentukan sebelumnya &&, || ,! dan
If
klausa bawaan.Anda dapat membaca definisi ini seperti definisi fungsi, tetapi arti sebenarnya adalah: Jika sebuah ekspresi cocok dengan pola yang dijelaskan di sisi kiri, itu diganti dengan ekspresi di sisi kanan. Anda bisa menentukan sendiri jika-klausa seperti ini:
SetAttributes[..., HoldRest]
memberi tahu evaluator bahwa ia harus mengevaluasi argumen pertama sebelum pencocokan pola, tetapi tahan evaluasi untuk sisanya sampai setelah pola dicocokkan dan diganti.Ini digunakan secara luas di dalam perpustakaan standar Mathematica untuk misalnya mendefinisikan fungsi
D
yang mengambil ekspresi dan mengevaluasi turunan simbolisnya.sumber
Metalua adalah bahasa dan kompiler yang kompatibel dengan Lua yang menyediakan ini.
Perbedaan dengan Lisp:
Contoh aplikasi adalah penerapan pencocokan pola seperti-ML.
Lihat juga: http://lua-users.org/wiki/MetaLua
sumber
Jika Anda mencari bahasa yang dapat diperpanjang, Anda harus melihat Smalltalk.
Di Smalltalk, satu - satunya cara memprogram adalah benar-benar memperluas bahasa. Tidak ada perbedaan antara IDE, perpustakaan atau bahasa itu sendiri. Mereka semua begitu terjalin sehingga Smalltalk sering disebut sebagai lingkungan daripada sebagai bahasa.
Anda tidak menulis aplikasi yang berdiri sendiri di Smalltalk, Anda malah memperluas lingkungan bahasa.
Lihatlah http://www.world.st/ untuk mengetahui beberapa sumber daya dan informasi.
Saya ingin merekomendasikan Pharo sebagai dialek masuk ke dunia Smalltalk: http://pharo-project.org
Semoga ini bisa membantu!
sumber
Ada alat yang memungkinkan membuat bahasa khusus tanpa menulis seluruh kompiler dari awal. Misalnya ada Spoofax , yang merupakan alat transformasi kode: Anda memasukkan tata bahasa input dan aturan transformasi (ditulis dengan cara deklaratif tingkat yang sangat tinggi), dan kemudian Anda dapat menghasilkan kode sumber Java (atau bahasa lain, jika Anda cukup peduli) dari bahasa khusus yang dirancang oleh Anda.
Jadi, dimungkinkan untuk mengambil tata bahasa bahasa X, mendefinisikan tata bahasa bahasa X '(X dengan ekstensi khusus Anda) dan transformasi X' → X, dan Spoofax akan menghasilkan kompiler X '→ X.
Saat ini, jika saya mengerti dengan benar, dukungan terbaik adalah untuk Java, dengan dukungan C # sedang dikembangkan (atau begitulah yang saya dengar). Teknik ini dapat diterapkan pada bahasa apa pun dengan tata bahasa statis (jadi, misalnya mungkin bukan Perl ).
sumber
Keempat adalah bahasa lain yang sangat mudah dikembangkan. Banyak implementasi Forth terdiri dari kernel kecil yang ditulis dalam assembler atau C, kemudian bahasa lainnya ditulis dalam Forth sendiri.
Ada juga beberapa bahasa berbasis tumpukan yang terinspirasi oleh Forth dan berbagi fitur ini, seperti Factor .
sumber
Funge-98
Fitur sidik jari Funge-98 memungkinkan dapat dilakukan untuk memungkinkan restrukturisasi lengkap seluruh sintaks dan semantik bahasa. Tetapi hanya jika implementor menyediakan mekanisme sidik jari yang memungkinkan pengguna untuk mengubah bahasa pemrograman secara terprogram (ini secara teori dimungkinkan untuk diterapkan dalam sintaks dan semantik Funge-98 normal). Jika demikian, seseorang dapat benar-benar membuat sisa file (atau bagian file apa pun) bertindak sebagai C ++ atau Lisp (atau apa pun yang diinginkannya).
http://quadium.net/funge/spec98.html#Fingerprints
sumber
Untuk mendapatkan apa yang Anda cari, Anda benar-benar membutuhkan tanda kurung dan kurangnya sintaksis. Beberapa bahasa berbasis sintaksis mungkin saja mendekati, tetapi itu tidak persis sama dengan makro yang sebenarnya.
sumber