LISP ของ McCarthy


39

McCarthy's LISP 2502

ในต้นปี พ.ศ. 2502 จอห์นแม็คคาร์ธีได้เขียนบทความที่แปลกใหม่ซึ่งกำหนดฟังก์ชั่นดั้งเดิมเพียงเก้าอย่างที่เมื่อรวมเข้าด้วยกันยังคงเป็นพื้นฐานสำหรับภาษา LISP ที่เหมือนกันทุกวันนี้ กระดาษมีดิจิทัลที่นี่:

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

งานของคุณคือการรองรับการใช้ตัวแยกวิเคราะห์และล่าม LISP แมคคาร์ของตรงตามที่อธิบายไว้ในกระดาษ 1960: นั่นคือฟังก์ชั่นQUOTE, ATOM, EQ, CAR, CDR, CONS, COND, LAMBDAและLABELทั้งหมดควรจะทำงาน กระดาษจะมีความสำคัญเหนือข้อความท้าทายนี้เมื่อพิจารณาความถูกต้องของคำตอบ แต่ฉันได้พยายามสรุปฟังก์ชั่นทั้งเก้าด้านล่าง โปรดทราบว่าภาษาจะอยู่ใน CAPS ทั้งหมดและไม่จำเป็นต้องตรวจสอบข้อผิดพลาดอินพุตทั้งหมดควรถูกสันนิษฐานว่าใช้งานได้

ประเภท

  • มีอยู่สองประเภทใน LISP ของ McCarthy: อะตอมและลิสต์ที่เชื่อมโยงซึ่งนิยามไว้แบบวนซ้ำซึ่งอาจเป็นลิสต์หรืออะตอมและลิสต์ที่แนบกับหัว (หาง) NILมีคุณสมบัติพิเศษของการเป็นทั้งอะตอมและรายการ
  • ตามชื่อกระดาษชื่ออะตอมจะประกอบด้วยตัวอักษรพิมพ์ใหญ่ตัวเลขและอักขระเว้นวรรคเท่านั้นแม้ว่าสตริงของช่องว่างที่ต่อเนื่องกันควรได้รับการพิจารณาว่าเป็นเพียงหนึ่งช่องว่างและควรลบอักขระช่องว่างด้านหน้าและด้านหลังทั้งหมด ตัวอย่างชื่อเทียบเท่าอะตอม ___ATOM__1__ = ATOM_1(แทนที่ขีดด้วยอักขระช่องว่าง): ตัวอย่างชื่ออะตอมที่ไม่เทียบเท่า:A_TOM_1 != ATOM_1
  • รายการถูกแสดงด้วยวงเล็บและโดยนัยNILอยู่ที่ท้ายรายการทุกรายการ องค์ประกอบในรายการคั่นด้วยเครื่องหมายจุลภาคและไม่ใช่ช่องว่างเหมือนกับใน Lisps ที่ทันสมัยที่สุด ดังนั้นรายการจะเป็น(ATOM 1, (ATOM 2)){[ATOM 1] -> {[ATOM 2] -> NIL} -> NIL}

QUOTE:

  • รับอาร์กิวเมนต์หนึ่งตัวซึ่งอาจเป็นอะตอม (องค์ประกอบเดียว) หรือรายการที่เชื่อมโยง ส่งคืนอาร์กิวเมนต์อย่างแน่นอน
  • กรณีทดสอบ:
  • (QUOTE, ATOM 1) -> ATOM 1
  • (QUOTE, (ATOM 1, ATOM 2)) -> (ATOM 1, ATOM 2)

ATOM:

  • รับอาร์กิวเมนต์หนึ่งตัวซึ่งอาจเป็นอะตอม (องค์ประกอบเดียว) หรือรายการที่เชื่อมโยง ส่งคืนT(จริง) ถ้าอาร์กิวเมนต์เป็นอะตอมหรือNIL(เท็จ) หากอาร์กิวเมนต์ไม่ใช่อะตอม
  • กรณีทดสอบ:
  • (ATOM, (QUOTE, ATOM 1)) -> T
  • (ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL

EQ:

  • รับอาร์กิวเมนต์สองตัวที่ต้องเป็นอะตอม (พฤติกรรมไม่ได้กำหนดไว้หากอาร์กิวเมนต์ตัวใดตัวหนึ่งไม่ใช่อะตอม) ส่งคืนT(จริง) หากทั้งสองอะตอมมีค่าเท่ากันหรือNIL(เท็จ) หากไม่เป็นเช่นนั้น
  • กรณีทดสอบ:
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 1)) -> T
  • (EQ, (QUOTE, ATOM 1), (QUOTE, ATOM 2)) -> NIL

CAR:

  • รับอาร์กิวเมนต์หนึ่งอันซึ่งต้องเป็นรายการ (พฤติกรรมไม่ได้กำหนดหากไม่ใช่รายการ) ส่งคืนอะตอม (หัว) แรกของรายการนั้น
  • กรณีทดสอบ:
  • (CAR, (QUOTE, (ATOM 1, ATOM 2))) -> ATOM 1

CDR:

  • รับอาร์กิวเมนต์หนึ่งอันซึ่งต้องเป็นรายการ (พฤติกรรมไม่ได้กำหนดหากไม่ใช่รายการ) ส่งคืนทุกอะตอมยกเว้นอะตอมแรกของรายการนั่นคือหาง หมายเหตุว่าทุกรายการในปลายนัยNILเพื่อให้การทำงานในรายการที่ปรากฏมีเพียงองค์ประกอบหนึ่งจะกลับมาCDRNIL
  • กรณีทดสอบ:
  • (CDR, (QUOTE, (ATOM 1, ATOM 2))) -> (ATOM 2)
  • (CDR, (QUOTE, (ATOM 1))) -> NIL

CONS:

  • รับอาร์กิวเมนต์สองตัว ครั้งแรกอาจจะเป็นอะตอมหรือรายการ NILแต่ที่สองจะต้องมีรายการหรือ ผนวกอาร์กิวเมนต์แรกเข้ากับอาร์กิวเมนต์ที่สองและส่งกลับรายการที่สร้างขึ้นใหม่
  • กรณีทดสอบ:
  • (CONS, (QUOTE, ATOM 1), (QUOTE, NIL)) -> (ATOM 1)
  • (CONS, (QUOTE, ATOM 1), (CONS, (QUOTE, ATOM 2), (QUOTE, NIL))) -> (ATOM 1, ATOM 2)

COND:

  • นี่คือคำสั่ง "if-else" ของ LISP รับจำนวนอากิวเมนต์ที่มีความยาวผันแปรซึ่งแต่ละรายการต้องเป็นรายการความยาวที่แน่นอน 2 สำหรับแต่ละรายการอาร์กิวเมนต์ตามลำดับประเมินคำแรกและถ้าเป็นจริง (T) ให้ส่งคืนคำที่สองที่เกี่ยวข้องและออกจากฟังก์ชัน . หากคำแรกไม่เป็นจริงให้ย้ายไปยังอาร์กิวเมนต์ถัดไปและทดสอบเงื่อนไขของมันและต่อไปเรื่อย ๆ จนกว่าจะถึงเงื่อนไขจริงแรก อย่างน้อยหนึ่งในเงื่อนไขการโต้แย้งสามารถสันนิษฐานว่าเป็นจริง - หากพวกเขาเป็นเท็จทั้งหมดนี่คือพฤติกรรมที่ไม่ได้กำหนด ดูหน้า 4 สำหรับตัวอย่างที่ดีเกี่ยวกับพฤติกรรมของฟังก์ชันนี้
  • กรณีทดสอบ:
  • (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:

  • กำหนดฟังก์ชั่นที่ไม่ระบุชื่อ รับอาร์กิวเมนต์สองตัวสิ่งแรกคือรายการของอะตอมซึ่งแสดงถึงข้อโต้แย้งของฟังก์ชันและอันดับที่สองคือ S-expression (ตัวฟังก์ชัน) ซึ่งโดยทั่วไปจะใช้อาร์กิวเมนต์
  • กรณีทดสอบ:
  • การกำหนดและใช้ฟังก์ชัน "isNull" แบบไม่ระบุชื่อ:
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> T
  • ((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, ATOM 1)) -> NIL

LABEL:

  • ให้ชื่อไปยังไม่ระบุชื่อฟังก์ชั่นซึ่งยังช่วยให้ฟังก์ชั่นที่จะถูกเรียกซ้ำในร่างกายของLAMBDA LAMBDAรับอาร์กิวเมนต์สองตัวสิ่งแรกคือเลเบลและอันดับที่สองเป็นLAMBDAฟังก์ชันที่เลเบลควรถูกโยง ส่งคืนชื่อที่ให้มา ขอบเขตของLABELชื่อทั้งหมดเป็นแบบโกลบอลและการนิยามใหม่LABELคือพฤติกรรมที่ไม่ได้กำหนด
  • ความจริงแล้วสนุกLABELไม่จำเป็นต้องสร้างฟังก์ชั่นแบบเรียกซ้ำเนื่องจากเรารู้ว่าLAMBDAสามารถใช้กับ'Y-Combinator'เพื่อทำงานนี้ให้สำเร็จ แต่ McCarthy ไม่ทราบวิธีนี้เมื่อเขียนบทความต้นฉบับ มันทำให้โปรแกรมเขียนได้ง่ายขึ้นมาก
  • กรณีทดสอบ:
  • (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
  • (หลังจากทำงานด้านบน) (SUBST, (QUOTE, A), (QUOTE, B), (QUOTE, (A, B, C))) -> (A, A, C)

เพื่อช่วยให้เห็นภาพของSUBSTฟังก์ชั่นด้านบนมันอาจถูกแสดงเป็น pseudocode เหมือน Python นี้:

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:])

กรณีทดสอบสุดท้าย:

หากฉันถอดความอย่างถูกต้องล่ามของคุณควรสามารถตีความEVALด้วยรหัสนี้ได้:

(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))))))

หลังจากเรียกใช้ behemoth แล้วบรรทัดนี้ควรกลับมา(A, B, C):

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

อย่างไรก็ตามในการอ้างถึง John McCarthy ในหน้า 16 ดูเหมือนว่าตัวละครของเขาจะหมดในคอมพิวเตอร์:

หากมีตัวละครเพิ่มเติมในคอมพิวเตอร์ก็จะสามารถปรับปรุงได้มาก ...

ดังนั้นความท้าทายนี้จึงถูกติดแท็กและคำตอบที่สั้นที่สุดในตัวละครจะเป็นผู้ชนะ ช่องโหว่มาตรฐานใช้ โชคดี!

หมายเหตุเกี่ยวกับ String Evals : (eval)ผมเข้าใจว่าบางคนคิดว่ามันอาจจะเป็นไปได้ที่จะชนะความท้าทายนี้โดยใช้เสียงกระเพื่อมไวยากรณ์และการปรับเปลี่ยนเพื่อให้พอดีกับภาษาโฮสต์แล้วใช้สตริง ฉันไม่มั่นใจเป็นอย่างยิ่งว่าวิธีการนี้จะต้องชนะโดยเฉพาะอย่างยิ่งกับกฎการตั้งชื่อตัวระบุและแม้ว่าฉันจะคิดว่าการห้ามใช้สตริงevals ในทุกภาษาจะเป็นความชันส่วนตัวและลื่น แต่ฉันไม่ต้องการที่จะลงโทษผู้คนที่ทำสิ่งนี้อย่างถูกวิธีดังนั้นฉันอาจอนุญาตให้ผู้ชนะสองคนสำหรับความท้าทายนี้หนึ่งในภาษาที่เหมือนเสียงกระเพื่อม .


1
คุณมีตัวอย่างของแลมบ์ดาที่กำหนดฟังก์ชั่น "IsNull" แต่ดูเหมือนว่า Nil return Nil เมื่อฉันรู้สึกว่ามันควรจะส่งคืน T หรือไม่
nmjcman101

1
คุณมี((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NILที่ไหน(QUOTE NIL)ในตอนท้ายคือการป้อนข้อมูลเช่นนี้ควรจะกลับT?
nmjcman101

1
ถูกต้อง แต่คุณเขียนแล้ว-> NIL
nmjcman101

1
ในคำอธิบายของCONSคุณคุณจะพูดว่า "ผนวกอาร์กิวเมนต์แรกกับอาร์กิวเมนต์ที่สองและส่งกลับรายการที่สร้างขึ้นใหม่" แต่กรณีทดสอบแสดงอาร์กิวเมนต์ที่สองที่ผนวกเข้ากับอาร์กิวเมนต์แรก อันไหนถูกต้อง?
จอร์แดน

1
ฉันกำลังใช้งานการสอนแบบ lisp ของ kjetilvalle และซินแทกซ์นั้นแตกต่างกันเล็กน้อย ใช้ตัวพิมพ์เล็กและไม่มีเครื่องหมายจุลภาค ฉันสามารถรันการแปลงตัวพิมพ์เล็กและลบเครื่องหมายจุลภาคจากสตริงอินพุตเพื่อให้สอดคล้องกับการออกแบบของล่ามด้านบนมากขึ้นหรือน้อยลงได้ไหม ฉันค่อนข้างใหม่กับ Lisp แต่ต้องการสำรวจความท้าทายนี้ด้วยภาษาของฉันเอง จนถึงขณะนี้ผมได้ตัวแยกวิเคราะห์ที่นำมาใช้ (ภาษาของฉันดูเหมือน Lisp แต่มีการใช้งานใน Node.js)
Andrakis

คำตอบ:


17

Python 3, 770 ไบต์

นี่คือ REPL บน stdin / stdout คาดว่าทุกบรรทัดจะเป็นคำสั่งแบบเต็มหรือว่างเปล่า evalใช้เพื่อย่นการนำไปใช้ให้สั้นลง แต่ไม่จำเป็นต้องใช้ตรรกะ

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 สองกรณีทดสอบแรกทำงานหลังจากแก้ไขข้อบกพร่องเล็ก ๆ ที่ฉันแนะนำในสัมผัสสุดท้าย Eval ทำงานได้อย่างไร้ที่ติ แต่SUBSTตัวอย่างก็ยังขาดอยู่ (สำหรับความรู้ของฉัน) ในฐานะ testcase หนึ่งในCONDs Tถึงจุดสิ้นสุดก่อนที่จะหาได้
orlp

1
ขอบคุณสำหรับการแก้ไข! มันน่าประทับใจมาก! มันใช้งานได้สำหรับฉันในทุก testcases ตอนนี้รวมถึงEVAL(ฉันประหลาดใจมากที่ฉันได้สิ่งนั้นในการลองครั้งแรก!) ฉันจะให้รางวัลแก่คุณและคำตอบที่ได้รับการยอมรับในขณะนี้!
แฮร์รี่

2
นอกจากนี้ฉันชอบการR(E(P(l)ตั้งค่า ;-)
แฮร์รี่

2
@ แฮร์รี่ฉันคิดว่าคุณไม่ได้เป็นอุบัติเหตุ! R = repr, E = eval, P = parsel line=
orlp

4
แค่อยากให้คุณรู้ว่าฉันเขียนบทความเกี่ยวกับการดำเนินการของคุณที่นี่ !
แฮร์รี่
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.