麦卡锡的LISP


39

麦卡锡的1959 LISP

1959年初,约翰·麦卡锡(John McCarthy)撰写了一篇具有开创性的论文,其中仅定义了9种原始函数,这些函数组合在一起仍然构成了当今所有LISP类语言的基础。该文件可在此处数字化:

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

你的任务是全面落实正是在1960年的论文中描述McCarthy的LISP解析器和解释:也就是说,功能QUOTEATOMEQCARCDRCONSCONDLAMBDA,和LABEL都应该发挥作用。考虑到答案的正确性,本文将优先于本挑战性文章,但我尝试总结以下九个功能。请注意,该语言将为ALL CAPS,并且无需进行错误检查,所有输入均应假定为有效。

种类

  • 麦卡锡的LISP中只有两种类型:原子和链表,它被递归地定义为头(可以是列表或原子),以及头连接到(尾)的列表。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如果参数是原子,则返回(true),如果参数不是原子,则返回(NILfalse)。
  • 测试用例:
  • (ATOM, (QUOTE, ATOM 1)) -> T
  • (ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL

EQ

  • 接受两个必须是原子的参数(如果两个参数中的任何一个都不是原子,则行为是不确定的)。T如果两个原子相等,则返回(true),否则,则返回(NILfalse)。
  • 测试用例:
  • (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,因此将CDR在似乎只有一个元素的列表上运行NIL
  • 测试用例:
  • (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

  • 这是LISP的“ if-else”语句。接受数量可变的参数,每个参数必须是一个长度恰好为2的列表。对于每个参数列表,按顺序计算第一个项,如果它是true(T),则返回关联的第二个项并退出函数。如果第一个条件不正确,则继续进行下一个参数并测试其条件,依此类推,直到达到第一个条件。至少一个自变量条件可以假定为true-如果它们全部为false,则这是未定义的行为。有关此功能行为的一个良好示例,请参见第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表达式(函数主体)。
  • 测试用例:
  • 定义和使用匿名“ 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名称的范围是全局的,重新定义a 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上面的函数,可以将其表示为类似于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))))))

运行该庞然大物之后,此行应返回(A, B, C)

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

但是,在第16页上引用John McCarthy本人的话,似乎他的计算机上的字符用完了:

如果计算机上有更多字符可用,则可以大大改进...

因此,此挑战被标记为,而最短答案的字符将是获胜者。有标准漏洞。祝好运!

关于字符串评估的注意事项:我知道有人认为,可以通过使用Lisp并修改语法以适合宿主语言,然后使用字符串来克服这一挑战(eval)。我并不特别相信这种方法必定会在标识符命名规则中胜出,即使我确实认为禁止eval所有语言的string 也是主观的和滑溜的。但是我不想惩罚以“正确”方式进行挑战的人,因此,如果这成为问题,我可以允许两名优胜者参加此挑战,其中一种使用类似Lisp的语言,另一种使用非Lispy的语言。 。


1
您有一个Lambda示例,定义了“ IsNull”函数,但它看起来像Nil返回Nil,在我看来它应该返回T?
nmjcman101 '16

1
您输入的末尾是((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NIL哪里(QUOTE NIL),因此应该返回T
nmjcman101 '16

1
对,但是您已经写了-> NIL
nmjcman101 '16

1
在描述中,CONS您说“将第一个参数附加到第二个参数并返回新创建的列表”,但是测试用例显示第二个参数附加到第一个参数。哪个是正确的?
乔丹

1
我的实现基于kjetilvalle的lisp教程,语法略有不同。使用小写字母,没有逗号。我可以简单地运行一个小写转换并从输入字符串中删除逗号,以使它或多或少地符合上述解释器的设计吗?我对Lisp还是很陌生,但是想以自己的语言探讨这一挑战。到目前为止,我已经实现了解析器。(我的语言看起来像Lisp,但是在Node.js中实现)
Andrakis

Answers:


17

Python 3,770个字节

这是在stdin / stdout上的REPL。期望每一行都是完整的语句或为空。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前两个测试用例在修复了我在最后的文章中介绍的一个小错误之后起作用。评估工作完美无缺。但是SUBST(据我所知)该示例仍被当作测试用例。的其中一个COND到达末尾,然后找到一个T
orlp 2016年

1
感谢您解决该问题!这非常令人印象深刻!它现在适用于所有测试用例,包括EVAL(非常令人惊讶的是我在第一次尝试时就正确了!)现在我将奖励您赏金和被接受的答案!
哈里

2
我也喜欢这个R(E(P(l)设置;-)
哈里

2
@哈里,我骗你不是偶然的!R = repr,E = eval,P = parse,l = line
orlp

4
只是想让您知道,我在这里写了一篇文章提到您的实现!
哈里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.