Jika yang Anda inginkan hanyalah bahasa yang diketik secara statis yang tampak seperti Lisp, Anda dapat melakukannya dengan mudah, dengan menentukan pohon sintaks abstrak yang mewakili bahasa Anda dan kemudian memetakan AST tersebut ke ekspresi-S. Namun, saya tidak berpikir saya akan menyebut hasilnya Lisp.
Jika Anda menginginkan sesuatu yang benar-benar memiliki karakteristik Lisp-y selain sintaks, dimungkinkan untuk melakukan ini dengan bahasa yang diketik secara statis. Namun, ada banyak karakteristik untuk Lisp yang sulit untuk mendapatkan banyak pengetikan statis berguna. Untuk mengilustrasikannya, mari kita lihat struktur daftar itu sendiri, yang disebut kontra , yang merupakan blok penyusun utama Lisp.
Menyebut kontra daftar, meskipun (1 2 3)
terlihat seperti satu, adalah sedikit keliru. Misalnya, ini sama sekali tidak dapat dibandingkan dengan daftar yang diketik secara statis, seperti daftar C ++ std::list
atau Haskell. Itu adalah daftar tertaut satu dimensi yang semua selnya berjenis sama. Lisp dengan senang hati mengizinkan (1 "abc" #\d 'foo)
. Plus, bahkan jika Anda memperluas daftar yang diketik statis untuk mencakup daftar-daftar, tipe objek ini mengharuskan setiap elemen dari daftar adalah sub-daftar. Bagaimana Anda mewakili ((1 2) 3 4)
mereka?
Lisp kerucut membentuk pohon biner, dengan daun (atom) dan cabang (kerucut). Selanjutnya, daun dari pohon seperti itu mungkin mengandung semua jenis Lisp atomik (non-kontra)! Fleksibilitas struktur inilah yang membuat Lisp begitu baik dalam menangani komputasi simbolik, AST, dan mengubah kode Lisp itu sendiri!
Jadi bagaimana Anda memodelkan struktur seperti itu dalam bahasa yang diketik secara statis? Mari kita coba di Haskell, yang memiliki sistem tipe statis yang sangat kuat dan tepat:
type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons
| CAtom Atom
Masalah pertama Anda adalah cakupan tipe Atom. Jelas, kami belum memilih jenis Atom dengan fleksibilitas yang cukup untuk mencakup semua jenis objek yang ingin kami selempang secara conses. Alih-alih mencoba memperluas struktur data Atom seperti yang tercantum di atas (yang dapat Anda lihat dengan jelas adalah rapuh), katakanlah kami memiliki kelas tipe magis Atomic
yang membedakan semua tipe yang kami ingin buat menjadi atom. Kemudian kami dapat mencoba:
class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons
| CAtom a
Tetapi ini tidak akan berhasil karena membutuhkan semua atom di pohon memiliki tipe yang sama . Kami ingin mereka dapat berbeda dari daun ke daun. Pendekatan yang lebih baik membutuhkan penggunaan bilangan eksistensial Haskell :
class Atomic a where ?????
data Cons = CCons Cons Cons
| forall a. Atomic a => CAtom a
Tapi sekarang Anda sampai pada inti masalahnya. Apa yang dapat Anda lakukan dengan atom dalam struktur seperti ini? Struktur apa yang mereka miliki yang dapat dimodelkan Atomic a
? Tingkat keamanan tipe apa yang Anda jamin dengan tipe seperti itu? Perhatikan bahwa kami belum menambahkan fungsi apa pun ke kelas tipe kami, dan ada alasan bagus: atom tidak memiliki kesamaan di Lisp. Supertipe mereka di Lisp hanya disebut t
(yaitu top).
Untuk menggunakannya, Anda harus menemukan mekanisme untuk secara dinamis memaksa nilai atom menjadi sesuatu yang benar-benar dapat Anda gunakan. Dan pada saat itu, pada dasarnya Anda telah menerapkan subsistem yang diketik secara dinamis dalam bahasa yang Anda ketik secara statis! (Seseorang tidak bisa tidak mencatat kemungkinan akibat wajar dari Aturan Pemrograman Kesepuluh Greenspun .)
Perhatikan bahwa Haskell menyediakan dukungan hanya untuk subsistem dinamis dengan Obj
tipe, yang digunakan bersama dengan Dynamic
tipe dan kelas Typeable untuk menggantikan Atomic
kelas kita , yang memungkinkan nilai arbitrer disimpan dengan tipenya, dan paksaan eksplisit kembali dari tipe tersebut. Itulah jenis sistem yang perlu Anda gunakan untuk bekerja dengan struktur kontra Lisp secara umum penuh.
Yang juga dapat Anda lakukan adalah pergi ke arah lain, dan menanamkan subsistem yang diketik secara statis dalam bahasa yang diketik secara dinamis. Ini memungkinkan Anda memanfaatkan pemeriksaan tipe statis untuk bagian-bagian program Anda yang dapat memanfaatkan persyaratan tipe yang lebih ketat. Ini tampaknya menjadi pendekatan yang diambil dalam bentuk terbatas CMUCL dari pemeriksaan jenis yang tepat , misalnya.
Terakhir, ada kemungkinan memiliki dua subsistem terpisah, yang diketik secara dinamis dan statis, yang menggunakan pemrograman gaya kontrak untuk membantu menavigasi transisi di antara keduanya. Dengan cara itu bahasa dapat mengakomodasi penggunaan Lisp di mana pemeriksaan tipe statis akan lebih menjadi penghalang daripada bantuan, serta penggunaan di mana pemeriksaan tipe statis akan menguntungkan. Ini adalah pendekatan yang diambil oleh Typed Racket , seperti yang akan Anda lihat dari komentar berikut.