LISP của McCarthy


39

McCarthy 1959 LISP

Đầu năm 1959, John McCarthy đã viết một bài báo đột phá xác định chỉ chín chức năng nguyên thủy mà khi kết hợp lại với nhau vẫn là cơ sở cho tất cả các ngôn ngữ giống như LISP ngày nay. Bài viết có sẵn được số hóa tại đây:

http://www-formal.stanford.edu/jmc/recursive.pdf

Công việc của bạn là để thực hiện đầy đủ một phân tích cú pháp và thông dịch viên cho LISP McCarthy chính xác như được mô tả trong bài báo năm 1960: Đó là, các chức năng QUOTE, ATOM, EQ, CAR, CDR, CONS, COND, LAMBDA, và LABELnên tất cả được chức năng. Bài viết sẽ được ưu tiên hơn văn bản thách thức này khi xem xét tính chính xác của câu trả lời, nhưng tôi đã cố gắng tóm tắt chín chức năng dưới đây. Lưu ý rằng ngôn ngữ sẽ ở TẤT CẢ CAPS và không cần kiểm tra lỗi, tất cả các đầu vào nên được coi là hợp lệ.

Các loại

  • Chỉ có hai loại trong LISP của McCarthy: Một nguyên tử và một danh sách được liên kết, được định nghĩa đệ quy là một cái đầu, có thể là một danh sách hoặc một nguyên tử, và một danh sách mà đầu được gắn vào (đuôi). NILcó tài sản đặc biệt là cả một nguyên tử và một danh sách.
  • Theo bài báo, tên nguyên tử sẽ chỉ bao gồm chữ in hoa, số và ký tự khoảng trắng, mặc dù các chuỗi không gian liên tiếp nên được coi là chỉ một khoảng trắng và tất cả các ký tự khoảng trắng ở đầu và cuối phải được loại bỏ. Ví dụ tên nguyên tử tương đương (thay thế dấu gạch dưới bằng ký tự khoảng trắng) : ___ATOM__1__ = ATOM_1. Ví dụ không phải tên nguyên tử tương đương:A_TOM_1 != ATOM_1
  • Danh sách được biểu thị bằng dấu ngoặc đơn và ngụ ý NILở cuối mỗi danh sách. Các thành phần trong danh sách được phân tách bằng dấu phẩy và không phải khoảng trắng như trong hầu hết các Lisps hiện đại. Vì vậy, danh sách (ATOM 1, (ATOM 2))sẽ được {[ATOM 1] -> {[ATOM 2] -> NIL} -> NIL}.

QUOTE:

  • Đưa ra một đối số có thể là nguyên tử (phần tử đơn) hoặc danh sách được liên kết. Trả về chính xác đối số.
  • Các trường hợp thử nghiệm:
  • (QUOTE, ATOM 1) -> ATOM 1
  • (QUOTE, (ATOM 1, ATOM 2)) -> (ATOM 1, ATOM 2)

ATOM:

  • Đưa ra một đối số có thể là nguyên tử (phần tử đơn) hoặc danh sách được liên kết. Trả về T(true) nếu đối số là nguyên tử hoặc NIL(sai) nếu đối số không phải là nguyên tử.
  • Các trường hợp thử nghiệm:
  • (ATOM, (QUOTE, ATOM 1)) -> T
  • (ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL

EQ:

  • Có hai đối số phải là nguyên tử (hành vi không xác định nếu một trong hai đối số không phải là nguyên tử). Trả về T(đúng) nếu hai nguyên tử tương đương hoặc NIL(sai) nếu không.
  • Các trường hợp thử nghiệm:
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 1)) -> T
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 2)) -> NIL

CAR:

  • Đưa ra một đối số phải là một danh sách (hành vi không được xác định nếu nó không phải là danh sách). Trả về nguyên tử đầu tiên (đầu) của danh sách đó.
  • Các trường hợp thử nghiệm:
  • (CAR, (QUOTE, (ATOM 1, ATOM 2))) -> ATOM 1

CDR:

  • Đưa ra một đối số phải là một danh sách (hành vi không được xác định nếu nó không phải là danh sách). Trả về mọi nguyên tử trừ nguyên tử đầu tiên của danh sách, tức là đuôi. Lưu ý rằng mọi danh sách đều kết thúc trong một hàm ý NIL, do đó, việc chạy CDRtrên một danh sách dường như chỉ có một phần tử sẽ trả về NIL.
  • Các trường hợp thử nghiệm:
  • (CDR, (QUOTE, (ATOM 1, ATOM 2))) -> (ATOM 2)
  • (CDR, (QUOTE, (ATOM 1))) -> NIL

CONS:

  • Đưa ra hai lập luận. Đầu tiên có thể là một nguyên tử hoặc một danh sách nhưng thứ hai phải là một danh sách hoặc NIL. Chuẩn bị đối số thứ nhất cho đối số thứ hai và trả về danh sách mới được tạo.
  • Các trường hợp thử nghiệm:
  • (CONS, (QUOTE, ATOM 1), (QUOTE, NIL)) -> (ATOM 1)
  • (CONS, (QUOTE, ATOM 1), (CONS, (QUOTE, ATOM 2), (QUOTE, NIL))) -> (ATOM 1, ATOM 2)

COND:

  • Đây là tuyên bố sắp xếp "nếu khác" của LISP. Lấy số lượng đối số có độ dài thay đổi, mỗi đối số phải là danh sách có độ dài chính xác 2. Đối với mỗi danh sách đối số theo thứ tự, hãy đánh giá thuật ngữ đầu tiên và nếu nó đúng (T), hãy trả về thuật ngữ thứ hai được liên kết và thoát khỏi hàm . Nếu thuật ngữ đầu tiên không đúng, hãy chuyển sang đối số tiếp theo và kiểm tra điều kiện của nó, và cứ thế cho đến khi đạt được điều kiện đúng đầu tiên. Ít nhất một trong các điều kiện đối số có thể được coi là đúng - nếu tất cả chúng đều sai, đây là hành vi không xác định. Xem trang 4 để biết ví dụ hay về hành vi của chức năng này.
  • Các trường hợp thử nghiệm:
  • (COND, ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1)), ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2))) -> 1
  • (COND, ((ATOM, (QUOTE, (ATOM 1, ATOM 2))), (QUOTE, 2)), ((ATOM, (QUOTE, ATOM 1)), (QUOTE, 1))) -> 1

LAMBDA:

  • Xác định một hàm ẩn danh. Có hai đối số, đầu tiên là danh sách các nguyên tử đại diện cho các đối số cho hàm và thứ hai là bất kỳ biểu thức S nào (thân hàm), thường sử dụng các đối số.
  • Các trường hợp thử nghiệm:
  • Xác định và sử dụng hàm "isNull" ẩn danh:
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> T
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, ATOM 1)) -> NIL

LABEL:

  • Đặt tên cho một LAMBDAhàm ẩn danh , cũng cho phép hàm đó được gọi đệ quy trong phần thân của LAMBDA. Có hai đối số, đầu tiên là nhãn và thứ hai là LAMBDAhàm mà nhãn phải được ràng buộc. Trả về tên được cung cấp. Phạm vi của tất cả các LABELtên là toàn cầu và xác định lại a LABELlà hành vi không xác định.
  • Thực tế thú vị, LABELthực sự không cần thiết để tạo các hàm đệ quy vì hiện tại chúng ta biết LAMBDAcó thể được sử dụng với 'Bộ kết hợp Y' để thực hiện nhiệm vụ này, nhưng McCarthy không biết về phương pháp này khi viết bài báo gốc. Nó làm cho các chương trình dễ dàng hơn nhiều để viết anyways.
  • Các trường hợp thử nghiệm:
  • (LABEL, SUBST, (LAMBDA, (X, Y, Z), (COND, ((ATOM, Z), (COND, ((EQ, Y, Z), X), ((QUOTE, T), Z))), ((QUOTE, T), (CONS, (SUBST, X, Y, (CAR, Z)), (SUBST, X, Y, (CDR, Z))))))) -> SUBST
  • (sau khi chạy ở trên) (SUBST, (QUOTE, A), (QUOTE, B), (QUOTE, (A, B, C))) -> (A, A, C)

Để giúp trực quan hóa SUBSTchức năng ở trên, nó có thể được biểu diễn dưới dạng mã giả giống như Python này:

def substitute(x, y, z): # substitute all instances of y (an atom) with x (any sexp) in z
    if isAtom(z):
        if y == z:
            return x
        elif True: 
            return z
    elif True:
        return substitute(x,y,z[0]) + substitute(x,y,z[1:])

TRƯỜNG HỢP KIỂM TRA CUỐI CÙNG:

Nếu tôi đã phiên âm nó một cách chính xác, trình thông dịch của bạn sẽ có thể diễn giải EVALvới mã này:

(LABEL, CAAR, (LAMBDA, (X), (CAR, (CAR, X))))
(LABEL, CDDR, (LAMBDA, (X), (CDR, (CDR, X))))
(LABEL, CADR, (LAMBDA, (X), (CAR, (CDR, X))))
(LABEL, CDAR, (LAMBDA, (X), (CDR, (CAR, X))))
(LABEL, CADAR, (LAMBDA, (X), (CAR, (CDR, (CAR, X)))))
(LABEL, CADDR, (LAMBDA, (X), (CAR, (CDR, (CDR, X)))))
(LABEL, CADDAR, (LAMBDA, (X), (CAR, (CDR, (CDR, (CAR, X))))))

(LABEL, ASSOC, (LAMBDA, (X, Y), (COND, ((EQ, (CAAR, Y), X), (CADAR, Y)), ((QUOTE, T), (ASSOC, X, (CDR, Y))))))

(LABEL, AND, (LAMBDA, (X, Y), (COND, (X, (COND, (Y, (QUOTE, T)), ((QUOTE, T), (QUOTE, NIL)))), ((QUOTE, T), (QUOTE, NIL)))))
(LABEL, NOT, (LAMBDA, (X), (COND, (X, (QUOTE, NIL)), ((QUOTE, T), (QUOTE, T)))))

(LABEL, NULL, (LAMBDA, (X), (AND, (ATOM, X), (EQ, X, (QUOTE, NIL)))))

(LABEL, APPEND, (LAMBDA, (X, Y), (COND, ((NULL, X), Y), ((QUOTE, T), (CONS, (CAR, X), (APPEND, (CDR, X), Y))))))

(LABEL, LIST, (LAMBDA, (X, Y), (CONS, X, (CONS, Y, (QUOTE, NIL))))) 

(LABEL, PAIR, (LAMBDA, (X, Y), (COND, ((AND, (NULL, X), (NULL, Y)), (QUOTE, NIL)), ((AND, (NOT, (ATOM, X)), (NOT, (ATOM, Y))), (CONS, (LIST, (CAR, X), (CAR, Y)), (PAIR, (CDR, X), (CDR, Y)))))))

(LABEL, EVAL, (LAMBDA, (E, A), (COND, ((ATOM, E), (ASSOC, E, A)), ((ATOM, (CAR, E)), (COND, ((EQ, (CAR, E), (QUOTE, QUOTE)), (CADR, E)), ((EQ, (CAR, E), (QUOTE, ATOM)), (ATOM, (EVAL, ((CADR, E), A)))), ((EQ, (CAR, E), (QUOTE, EQ)), (EQ, (EVAL, (CADR, E, A)), (EVAL, (CADDR, E, A)))), ((EQ, (CAR, E), (QUOTE, COND)), (EVCON, (CDR, E), A)), ((EQ, (CAR, E), (QUOTE, CAR)), (CAR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CDR)), (CDR, (EVAL, (CADR, E), A))), ((EQ, (CAR, E), (QUOTE, CONS)), (CONS, (EVAL, (CADR, E), A), (EVAL, (CADDR, E), A))), ((QUOTE, T), (EVAL, (CONS, (ASSOC, (CAR, E), A), (EVLIS, (CDR, E), A)), A)))), ((EQ, (CAAR, E), (QUOTE, LABEL)), (EVAL, (CONS, (CADDAR, E), (CDR, E)), (CONS, (CONS, (CADAR, E), (CONS, (CAR, E), (CONS, A, (QUOTE, NIL))))))), ((EQ, (CAAR, E), (QUOTE, LAMBDA)), (EVAL, (CADDAR, E), (APPEND, (PAIR, (CADAR, E), (EVLIS, (CDR, E), A)), A))))))

(LABEL, EVCON, (LAMBDA, (C, A), (COND, ((EVAL, (CAAR, C), A), (EVAL, (CADAR, C), A)), ((QUOTE, T), (EVCON, (CDR, C), A)))))

(LABEL, EVLIS, (LAMBDA, (M, A), (COND, ((NULL, M), (QUOTE, NIL)), ((QUOTE, T), (CONS, (EVAL, (CAR, M), A), (EVLIS, (CDR, M), A))))))

Sau khi chạy chế độ đó, dòng này sẽ trở lại (A, B, C):

(EVAL, (QUOTE, (CONS, X, (QUOTE, (B, C)))), (QUOTE, ((X, A), (Y, B))))

Tuy nhiên, để trích dẫn chính John McCarthy ở trang 16, có vẻ như anh ta đã hết nhân vật trên máy tính của mình:

Nếu có nhiều ký tự hơn trên máy tính, nó có thể được cải thiện đáng kể ...

Do đó, thử thách này được gắn thẻ và câu trả lời ngắn nhất trong các nhân vật sẽ là người chiến thắng. Tiêu chuẩn áp dụng. Chúc may mắn!

Lưu ý về Chuỗi bằng chứng : Tôi hiểu rằng một số người nghĩ rằng có thể chiến thắng thử thách này bằng cách sử dụng Lisp và sửa đổi cú pháp để phù hợp với ngôn ngữ máy chủ và sau đó sử dụng chuỗi (eval). Tôi không đặc biệt tin rằng cách tiếp cận này nhất thiết sẽ giành chiến thắng đặc biệt là với các quy tắc đặt tên định danh và ngay cả khi tôi nghĩ rằng việc cấm chuỗi evals trong tất cả các ngôn ngữ sẽ là một độ dốc chủ quan và trơn trượt. Nhưng tôi không muốn trừng phạt mọi người vì đã thực hiện thử thách này theo cách 'đúng', vì vậy tôi có thể cho phép hai người chiến thắng cho thử thách này, một bằng ngôn ngữ giống Lisp và một bằng ngôn ngữ không phải là Lispy, nếu điều này trở thành vấn đề .


1
Bạn có một ví dụ Lambda xác định hàm "IsNull", nhưng có vẻ như Nil return Nil, khi tôi có vẻ như nó sẽ trả về T?
nmjcman101

1
Bạn có ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NILTrường hợp (QUOTE NIL)ở cuối là đầu vào, vì vậy điều này sẽ trở lại T?
nmjcman101

1
Đúng, nhưng bạn đã viết-> NIL
nmjcman101

1
Trong phần mô tả của CONSbạn, bạn nói "Nối đối số thứ nhất vào đối số thứ hai và trả về danh sách mới được tạo", nhưng các trường hợp thử nghiệm cho thấy đối số thứ hai được thêm vào đối số thứ nhất. Cái nào đúng?
Jordan

1
Tôi đang căn cứ vào việc thực hiện hướng dẫn lisp của kjetilvalle và cú pháp hơi khác. Chữ thường được sử dụng và không có dấu phẩy. Tôi có thể chỉ cần chạy một phép biến đổi chữ thường và xóa dấu phẩy khỏi chuỗi đầu vào để nó phù hợp ít nhiều với thiết kế của trình thông dịch ở trên không? Tôi khá mới mẻ với Lisp, nhưng muốn khám phá thử thách này bằng ngôn ngữ của riêng tôi. Cho đến nay tôi đã thực hiện trình phân tích cú pháp . (Ngôn ngữ của tôi trông giống Lisp, nhưng được triển khai trong Node.js)
Andrakis

Câu trả lời:


17

Python 3, 770 byte

Đây là một REPL trên stdin / stdout. Mong đợi mỗi dòng là một tuyên bố đầy đủ hoặc trống rỗng. evalđược sử dụng để rút ngắn việc thực hiện, nhưng nếu không thì không cần thiết cho logic.

import re,sys;S=re.sub
P=lambda l:eval(S("([A-Z0-9][A-Z0-9 ]*)",r"' '.join('\1'.strip().split())",S("NIL","()",S("\)",",)",l))))
d={"QUOTE":'(v,L[1])[1]',"EQ":'[(),"T"][E(L[1],v)==E(L[2],v)]',
"CDR":'E(L[1],v)[1:]',"CONS":'(E(L[1],v),)+E(L[2],v)',"CAR":'E(L[1],v)[0]',
"LAMBDA":'("#",)+L[1:]',"LABEL":'[v.update({L[1]:E(L[2],v)}),L[1]][1]'}
def E(L,v):
 if L*0=="":return v[L]
 elif L[0]in d:return eval(d[L[0]])
 elif L[0]=="COND":return next(E(l[1],v)for l in L[1:]if E(l[0],v)=="T")
 elif L[0]=="ATOM":o=E(L[1],v);return[(),"T"][o*0in["",o]]
 else:l=E(L[0],v);n=v.copy();n.update({a:E(p,v)for a,p in zip(l[1],L[1:])});return E(l[2],n)
R=lambda o:o==()and"NIL"or 0*o==()and"(%s)"%", ".join(R(e)for e in o)or o
g={}
for l in sys.stdin:
 if l.strip():print(R(E(P(l),g)))

1
@Harry Hai trường hợp thử nghiệm đầu tiên hoạt động sau khi sửa một lỗi nhỏ mà tôi đã giới thiệu trong lần chạm cuối cùng. Các eval hoạt động hoàn hảo. Nhưng SUBSTví dụ vẫn bị phá vỡ (theo hiểu biết của tôi) như một thử nghiệm. Một trong những CONDs đến cuối trước khi tìm thấy a T.
orlp

1
Cảm ơn bạn đã sửa nó! Điều này rất ấn tượng! Nó hoạt động với tôi trên tất cả các thử nghiệm bây giờ, bao gồm EVAL(rất ngạc nhiên khi tôi nhận được ngay lần thử đầu tiên!) Tôi sẽ trao thưởng cho bạn tiền thưởng và câu trả lời được chấp nhận ngay bây giờ!
Harry

2
Ngoài ra tôi thích R(E(P(l)thiết lập ;-)
Harry

2
@ Tôi không đùa bạn đó là một tai nạn! R = repr, E = eval, P = parse, l = line.
orlp

4
Chỉ muốn cho bạn biết, tôi đã viết một bài báo đề cập đến việc thực hiện của bạn ở đây !
Harry
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.