麦卡锡的1959 LISP
1959年初,约翰·麦卡锡(John McCarthy)撰写了一篇具有开创性的论文,其中仅定义了9种原始函数,这些函数组合在一起仍然构成了当今所有LISP类语言的基础。该文件可在此处数字化:
http://www-formal.stanford.edu/jmc/recursive.pdf
你的任务是全面落实正是在1960年的论文中描述McCarthy的LISP解析器和解释:也就是说,功能QUOTE
,ATOM
,EQ
,CAR
,CDR
,CONS
,COND
,LAMBDA
,和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),如果参数不是原子,则返回(NIL
false)。 - 测试用例:
(ATOM, (QUOTE, ATOM 1)) -> T
(ATOM, (QUOTE, (ATOM 1, ATOM 2))) -> NIL
EQ
:
- 接受两个必须是原子的参数(如果两个参数中的任何一个都不是原子,则行为是不确定的)。
T
如果两个原子相等,则返回(true),否则,则返回(NIL
false)。 - 测试用例:
(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
名称的范围是全局的,重新定义aLABEL
是未定义的行为。 - 有趣的是,
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的语言。 。
((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NIL
哪里(QUOTE NIL)
,因此应该返回T
?
-> NIL
CONS
您说“将第一个参数附加到第二个参数并返回新创建的列表”,但是测试用例显示第二个参数附加到第一个参数。哪个是正确的?