Nếu tất cả những gì bạn muốn là một ngôn ngữ được nhập tĩnh trông giống như Lisp, bạn có thể làm điều đó khá dễ dàng, bằng cách xác định một cây cú pháp trừu tượng đại diện cho ngôn ngữ của bạn và sau đó ánh xạ AST sang biểu thức S. Tuy nhiên, tôi không nghĩ rằng tôi sẽ gọi kết quả là Lisp.
Nếu bạn muốn một thứ gì đó thực sự có đặc điểm Lisp-y bên cạnh cú pháp, bạn có thể thực hiện điều này với ngôn ngữ được nhập tĩnh. Tuy nhiên, có rất nhiều đặc điểm đối với Lisp mà rất khó để loại bỏ tính năng gõ tĩnh hữu ích. Để minh họa, chúng ta hãy nhìn vào cấu trúc danh sách, được gọi là khuyết điểm , tạo thành khối xây dựng chính của Lisp.
Gọi khuyết điểm là một danh sách, mặc dù (1 2 3)
trông giống như một, là một chút nhầm lẫn. Ví dụ, nó hoàn toàn không thể so sánh với một danh sách được nhập tĩnh, như danh sách của C ++ std::list
hoặc Haskell. Đó là những danh sách được liên kết đơn chiều trong đó tất cả các ô đều thuộc cùng một loại. Lisp vui vẻ cho phép (1 "abc" #\d 'foo)
. Thêm vào đó, ngay cả khi bạn mở rộng danh sách được nhập tĩnh của mình để bao gồm danh sách danh sách, kiểu của các đối tượng này yêu cầu mọi phần tử của danh sách là một danh sách con. Bạn sẽ đại diện như thế nào((1 2) 3 4)
họ như thế nào?
Các khuyết điểm Lisp tạo thành một cây nhị phân, với các lá (nguyên tử) và các nhánh (các khuyết điểm). Hơn nữa, lá của một cái cây như vậy có thể chứa bất kỳ loại Lisp nguyên tử (không khuyết điểm) nào cả! Tính linh hoạt của cấu trúc này là điều làm cho Lisp rất tốt trong việc xử lý tính toán biểu tượng, AST và tự chuyển đổi mã Lisp!
Vì vậy, làm thế nào bạn sẽ mô hình một cấu trúc như vậy trong một ngôn ngữ được gõ tĩnh? Hãy thử nó trong Haskell, có một hệ thống loại tĩnh cực kỳ mạnh mẽ và chính xác:
type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons
| CAtom Atom
Vấn đề đầu tiên của bạn sẽ là phạm vi của loại Atom. Rõ ràng, chúng tôi đã không chọn một loại Atom đủ linh hoạt để bao phủ tất cả các loại đối tượng mà chúng tôi muốn bám vào các khuyết điểm. Thay vì cố gắng mở rộng cấu trúc dữ liệu Atom như được liệt kê ở trên (mà bạn có thể thấy rõ ràng là dễ vỡ), giả sử chúng ta có một lớp kiểu ma thuật Atomic
phân biệt tất cả các kiểu mà chúng ta muốn tạo nguyên tử. Sau đó, chúng tôi có thể thử:
class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons
| CAtom a
Nhưng điều này sẽ không hiệu quả vì nó yêu cầu tất cả các nguyên tử trong cây phải cùng loại. Chúng tôi muốn chúng có thể khác nhau giữa các lá. Một cách tiếp cận tốt hơn yêu cầu sử dụng các bộ định lượng hiện sinh của Haskell :
class Atomic a where ?????
data Cons = CCons Cons Cons
| forall a. Atomic a => CAtom a
Nhưng bây giờ bạn đi đến mấu chốt của vấn đề. Bạn có thể làm gì với các nguyên tử trong loại cấu trúc này? Chúng có điểm chung nào để có thể được mô hình hóa Atomic a
? Mức độ an toàn của loại bạn được đảm bảo với loại như vậy? Lưu ý rằng chúng tôi chưa thêm bất kỳ chức năng nào vào lớp kiểu của chúng tôi và có một lý do chính đáng: các nguyên tử không có điểm chung nào trong Lisp. Siêu kiểu của chúng trong Lisp được gọi đơn giản t
(tức là đỉnh).
Để sử dụng chúng, bạn phải nghĩ ra các cơ chế để cưỡng chế động giá trị của một nguyên tử với thứ mà bạn thực sự có thể sử dụng. Và tại thời điểm đó, về cơ bản bạn đã triển khai một hệ thống con được nhập động trong ngôn ngữ được nhập tĩnh của mình! (Người ta không thể không lưu ý đến một hệ quả có thể xảy ra đối với Quy tắc lập trình thứ mười của Greenspun .)
Lưu ý rằng Haskell chỉ cung cấp hỗ trợ cho một hệ thống con động như vậy với một Obj
loại, được sử dụng cùng với một Dynamic
loại và một lớp có thể đánh máy để thay thế Atomic
lớp của chúng tôi , cho phép các giá trị tùy ý được lưu trữ cùng với các loại của chúng và cưỡng chế rõ ràng từ các loại đó. Đó là loại hệ thống bạn cần sử dụng để làm việc với các cấu trúc khuyết điểm Lisp về tính tổng quát đầy đủ của chúng.
Những gì bạn cũng có thể làm là đi theo cách khác và nhúng một hệ thống con được nhập tĩnh trong một ngôn ngữ được nhập động về cơ bản. Điều này cho phép bạn hưởng lợi từ việc kiểm tra kiểu tĩnh đối với các phần của chương trình có thể tận dụng các yêu cầu kiểu nghiêm ngặt hơn. Đây dường như là cách tiếp cận được thực hiện trong hình thức kiểm tra kiểu chính xác hạn chế của CMUCL .
Cuối cùng, có khả năng có hai hệ thống con riêng biệt, được nhập động và tĩnh, sử dụng lập trình kiểu hợp đồng để giúp điều hướng quá trình chuyển đổi giữa hai hệ thống. Bằng cách đó, ngôn ngữ này có thể phù hợp với các cách sử dụng của Lisp, nơi việc kiểm tra kiểu tĩnh sẽ là một trở ngại nhiều hơn là một sự trợ giúp, cũng như những cách sử dụng mà việc kiểm tra kiểu tĩnh sẽ có lợi. Đây là cách tiếp cận được thực hiện bởi Typed Racket , như bạn sẽ thấy từ các bình luận sau đó.