LISP Маккарті


39

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). Я не особливо впевнений, що такий підхід обов'язково виграє, особливо з використанням правил іменування ідентифікатора, і навіть якщо б це було, я думаю, що заборона рядків 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
Я базую свою реалізацію на уроці kjetilvalle o lisp , і синтаксис трохи інший. Використовується малі літери, а комами немає. Чи можу я просто запустити перетворення малих літер і видалити коми з вхідного рядка, щоб воно більше чи менше відповідало вищевказаному дизайну інтерпретатора? Я досить новачок у Ліспі, але хотів дослідити цю проблему своєю рідною мовою. Поки що в мене парсер реалізований . (Моя мова схожа на Lisp, але реалізована в Node.js)
Андракіс

Відповіді:


17

Python 3, 770 байт

Це відповідь на 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 Перші два тестових справи працюють після виправлення невеликої помилки, яку я представив у фінальних дотиках. Евал працює бездоганно. Але SUBSTприклад все ще зламаний (наскільки мені відомо) як перевірка. Один з CONDs доходить до кінця, перш ніж знайти a T.
orlp

1
Дякую, що це виправили! Це дуже вражає! Зараз він працює на всіх тестах, в тому числі EVAL(настільки приємно здивований, що я отримав це право в першій спробі!) Я збираюся нагородити вас винагородою та прийнятою відповіддю зараз!
Гаррі

2
Також я люблю R(E(P(l)налаштування ;-)
Гаррі

2
@ Харі, я тебе маля, це не випадковість! R = repr, E = eval, P = parse, l = line.
orlp

4
Просто хотів вас повідомити, я написав статтю, в якій згадував про вашу реалізацію тут !
Гаррі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.