LISP 1959 року Маккарті
На початку 1959 р. Джон Маккарті написав новаторський документ, де визначив лише дев'ять примітивних функцій, які, складені разом, все ще складають основу для всіх мов, схожих на LISP. Документ оцифрований тут:
http://www-formal.stanford.edu/jmc/recursive.pdf
Ваше завдання полягає в повній мірі реалізувати синтаксичний аналізатор і інтерпретатор LISP Маккарті точно , як описано в роботі-1960: Тобто, функції QUOTE
, ATOM
, EQ
, CAR
, CDR
, CONS
, COND
, LAMBDA
, і LABEL
всі повинні бути функціональними. У статті буде перевага над цим текстом виклику при розгляді правильності відповідей, але я намагався узагальнити дев'ять функцій нижче. Зауважте, що мова буде у ВСІХ КАПС, і перевірка помилок не потрібна, всі введення повинні вважатись дійсними.
Типи
- У 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
(істинно), якщо два атоми еквівалентні, або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
, тому запуск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. Виймає кількість аргументів змінної довжини, кожен з яких повинен бути списком довжини рівно 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-вираз (тіло функції), який зазвичай використовує аргументи.
- Тестові приклади:
- Визначення та використання анонімної функції "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» , але Маккарті не знав про цей метод під час написання оригінального документа. Це робить програми набагато простіше писати. - Тестові приклади:
(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, схоже, у нього не вистачало символів на своєму комп’ютері:
Якщо на комп’ютері було доступно більше символів, це можна було б значно покращити ...
Тому цей виклик позначається код-гольф, і найкоротша відповідь символів буде переможцем. Застосовуються стандартні лазівки. Удачі!
Примітка до Stval Evals : Я розумію, що деякі вважають, що можливо виграти цей виклик, використовуючи Lisp та модифікуючи синтаксис відповідно до мови хосту, а потім використовуючи рядок (eval)
. Я не особливо впевнений, що такий підхід обов'язково виграє, особливо з використанням правил іменування ідентифікатора, і навіть якщо б це було, я думаю, що заборона рядків eval
s на всіх мовах буде суб'єктивним і слизьким нахилом. Але я не хочу карати людей за те, що вони роблять цей виклик "правильним" способом, тому я можу дозволити двом переможцям за цей виклик, одному на мові, що нагадує Лісп, та одному на неліспійській мові, якщо це стане проблемою. .
((LAMBDA, (ATOM 1), (EQ, ATOM 1, (QUOTE, NIL))), (QUOTE, NIL)) -> NIL
де (QUOTE NIL)
в кінці є вхід, тож це має повернутися T
?
-> NIL
CONS
ви говорите "додає перший аргумент до другого аргументу та повертає новостворений список", але тестові випадки показують, що другий аргумент додається до першого. Що правильно?