Có thể sử dụng biến thể Lisp đầy đủ được nhập tĩnh không?


107

Có thể sử dụng biến thể Lisp đầy đủ được nhập tĩnh không? Nó thậm chí có ý nghĩa cho một cái gì đó như thế này tồn tại? Tôi tin rằng một trong những ưu điểm của ngôn ngữ Lisp là tính đơn giản trong định nghĩa của nó. Nhập tĩnh có làm ảnh hưởng đến nguyên tắc cốt lõi này không?


10
Tôi thích các macro dạng tự do của Lisp, nhưng tôi thích sự mạnh mẽ của hệ thống kiểu của Haskell. Tôi muốn xem một Lisp được nhập tĩnh trông như thế nào.
mcandre

4
Câu hỏi hay! Tôi tin rằng shenlanguage.org làm được điều đó. Tôi ước nó trở nên chính thống hơn.
Hamish Grubijan


Làm thế nào để bạn thực hiện tính toán tượng trưng với Haskell? (giải 'x' (= (+ xy) (* xy))). Nếu bạn đặt nó trong một chuỗi thì không có kiểm tra (không giống như Lisp có thể sử dụng macro để thêm kiểm tra). Nếu bạn sử dụng kiểu dữ liệu đại số hoặc danh sách ... Sẽ rất dài dòng: giải quyết (Sym "x") (Eq (Cộng (Sym "x") (Sym "y")) (Mult (Sym "x") (Sym "y")))
aoeu256

Câu trả lời:


57

Có, rất có thể, mặc dù hệ thống kiểu HM tiêu chuẩn thường là lựa chọn sai đối với hầu hết các mã Lisp / Scheme thành ngữ. Xem Vợt đã nhập để biết ngôn ngữ gần đây là "Lisp đầy đủ" (thực ra giống như Scheme hơn) với tính năng nhập tĩnh.


1
Vấn đề ở đây là, loại danh sách tạo nên toàn bộ mã nguồn của một chương trình vợt được đánh máy là gì?
Zorf

18
Đó thường sẽ là Sexpr.
Eli Barzilay

Nhưng sau đó, tôi có thể viết coerce :: a->bvề đánh giá. Loại an toàn ở đâu?
ssice

2
@ssice: khi bạn đang sử dụng một hàm chưa định kiểu, evalbạn cần kiểm tra kết quả để xem điều gì xuất hiện, điều này không có gì mới trong Typed Racked (tương tự như một hàm có kiểu liên hợp là StringNumber). Một cách ngầm để thấy rằng điều này có thể được thực hiện là thực tế là bạn có thể viết và sử dụng một ngôn ngữ được gõ động bằng một ngôn ngữ được gõ tĩnh HM.
Eli Barzilay

37

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::listhoặ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 Atomicphâ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 Objloại, được sử dụng cùng với một Dynamicloại và một lớp có thể đánh máy để thay thế Atomiclớ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 đó.


16
Câu trả lời này gặp phải một vấn đề cơ bản: bạn đang giả định rằng các hệ thống kiểu tĩnh phải là kiểu HM. Khái niệm cơ bản không thể được thể hiện ở đó, và là một tính năng quan trọng của mã Lisp, là kiểu con. Nếu bạn nhìn vào vợt được đánh máy, bạn sẽ thấy rằng nó có thể dễ dàng thể hiện bất kỳ loại danh sách nào - bao gồm những thứ như (Listof Integer)(Listof Any). Rõ ràng, bạn nghi ngờ cái sau là vô dụng vì bạn không biết gì về kiểu, nhưng trong TR, sau này bạn có thể sử dụng (if (integer? x) ...)và hệ thống sẽ biết đó xlà Số nguyên trong nhánh thứ nhất.
Eli Barzilay

5
Ồ, và đó là một đặc điểm xấu của vợt đã định hình (khác với các hệ thống loại không chắc chắn mà bạn sẽ tìm thấy ở một số nơi). Typed Racket một ngôn ngữ được nhập tĩnh , không có chi phí thời gian chạy cho mã đã nhập. Racket vẫn cho phép viết một số mã bằng TR và một số mã bằng ngôn ngữ không định kiểu thông thường - và đối với những trường hợp này, các hợp đồng (kiểm tra động) được sử dụng để bảo vệ mã đã nhập khỏi mã không định kiểu tiềm ẩn.
Eli Barzilay

1
@Eli Barzilay: Tôi đã nói dối, có bốn phần: 4. Thật thú vị với tôi là phong cách mã hóa C ++ được ngành công nghiệp chấp nhận đã dần rời xa kiểu phụ sang kiểu chung. Điểm yếu là ngôn ngữ không cung cấp trợ giúp để khai báo giao diện mà một hàm chung sẽ sử dụng, một cái gì đó các lớp kiểu chắc chắn có thể giúp ích. Ngoài ra, C ++ 0x có thể thêm suy luận kiểu. Không phải HM, tôi cho là vậy, mà đang đi theo hướng đó?
Owen S.

1
Owen: (1) điểm chính là bạn cần các kiểu con để thể hiện loại mã người nói ngọng viết - và bạn chỉ không thể có điều đó với các hệ thống HM, vì vậy bạn buộc phải tùy chỉnh các kiểu và cấu trúc cho mỗi lần sử dụng, khiến toàn bộ điều này trở nên khó sử dụng hơn nhiều. Trong vợt đã nhập sử dụng hệ thống có các kiểu phụ là hệ quả của một quyết định thiết kế có chủ đích: kết quả phải có thể thể hiện các loại mã đó mà không cần thay đổi mã hoặc tạo các kiểu tùy chỉnh.
Eli Barzilay

1
(2) Đúng vậy, dynamiccác kiểu đang trở nên phổ biến trong các ngôn ngữ tĩnh như một cách giải quyết để có được một số lợi ích của các ngôn ngữ được nhập động, với sự đánh đổi thông thường các giá trị này được bao bọc theo cách làm cho các kiểu có thể nhận dạng được. Nhưng ở đây, vợt quá gõ đang làm rất tốt việc tạo sự thuận tiện trong ngôn ngữ - trình kiểm tra kiểu sử dụng sự xuất hiện của các vị từ để biết thêm về các kiểu. Ví dụ: xem ví dụ đã nhập trên trang vợt và xem cách string?"giảm" danh sách chuỗi và số thành danh sách chuỗi.
Eli Barzilay

10

Câu trả lời của tôi, không có mức độ tự tin cao có lẽ là . Ví dụ, nếu bạn nhìn vào một ngôn ngữ như SML và so sánh nó với Lisp, lõi chức năng của mỗi ngôn ngữ gần như giống hệt nhau. Do đó, có vẻ như bạn sẽ không gặp nhiều khó khăn khi áp dụng một số kiểu nhập tĩnh vào lõi của Lisp (ứng dụng hàm và các giá trị nguyên thủy).

Tuy nhiên, câu hỏi của bạn đã nói đầy đủ và nơi tôi thấy một số vấn đề đang xảy ra là cách tiếp cận mã dưới dạng dữ liệu. Các kiểu tồn tại ở mức trừu tượng hơn các biểu thức. Lisp không có sự phân biệt này - mọi thứ đều "phẳng" trong cấu trúc. Nếu chúng ta xem xét một số biểu thức E: T (trong đó T là một số đại diện cho kiểu của nó), và sau đó chúng ta coi biểu thức này là dữ liệu thuần túy, thì kiểu của T ở đây chính xác là gì? Chà, đó là một loại! Một loại là loại đơn hàng cao hơn, vì vậy chúng ta hãy tiếp tục và nói điều gì đó về điều đó trong mã của chúng tôi:

E : T :: K

Bạn có thể thấy tôi đang đi đâu với cái này. Tôi chắc chắn rằng bằng cách tách thông tin loại khỏi mã sẽ có thể tránh được kiểu tự tham chiếu này của các loại, tuy nhiên điều đó sẽ làm cho các loại không được "ngọng" lắm trong hương vị của chúng. Có lẽ có nhiều cách để giải quyết vấn đề này, mặc dù đối với tôi thì không rõ đâu là cách tốt nhất.

CHỈNH SỬA: Ồ, vì vậy với một chút googling, tôi đã tìm thấy Qi , có vẻ rất giống với Lisp ngoại trừ việc nó được nhập tĩnh. Có lẽ đó là một nơi tốt để bắt đầu xem họ đã thực hiện thay đổi ở đâu để nhập tĩnh vào đó.


Có vẻ như lần lặp lại tiếp theo sau Qi là Shen , được phát triển bởi cùng một người.
Diagon

4

Liên kết đã chết. Nhưng trong mọi trường hợp, Dylan không được nhập tĩnh.
Björn Lindqvist

@ BjörnLindqvist: liên kết đó là một luận điểm về việc thêm tính năng gõ dần dần vào Dylan.
Rainer Joswig

1
@ BjörnLindqvist: Tôi đã liên kết đến một bài báo tổng quan.
Rainer Joswig

Nhưng gõ dần dần không được tính là nhập tĩnh. Nếu đúng như vậy thì Pypy sẽ là Python được nhập tĩnh vì nó cũng sử dụng kiểu gõ dần dần.
Björn Lindqvist

2
@ BjörnLindqvist: nếu chúng ta thêm các kiểu tĩnh thông qua gõ dần dần và chúng được kiểm tra trong quá trình biên dịch, thì đây là kiểu nhập tĩnh. Nó không phải là toàn bộ chương trình được nhập tĩnh, mà là các phần / vùng. homes.sice.indiana.edu/jsiek/what-is-gradual-typing ' dần dần là một hệ thống kiểu mà tôi đã phát triển với Walid Taha vào năm 2006 cho phép các phần của chương trình được gõ động và các phần khác được gõ tĩnh. "
Rainer Joswig
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.