Алан Кэй Эваль/Применить момент Эйнштейна

Алан Кей сказал, что внимательно читает код и находит ошибку 1 и только в коде на стр. 13 руководства Lisp 1.5, помог ему понять Компьютерная наука в 100 раз лучше.

Этот код является 1-й версией eval и apply, которая выглядит как-то удаленно, как современный Lisp (о котором я знаю).

Поскольку правильный ответ, вероятно, известен, но потерян (мой google-fu приличный, и я искал хотя бы 20 минут) Я дам 1-й правильный ответ (я буду смотреть на время редактирования, поэтому не пытайтесь обмануть) 250-процентную щедрость как можно скорее.

Я бы предложил другим внести вклад в щедрость, помните из видео выше Алан Кей сказал, что этот материал напоминает окружающую среду Einstein, когда он обнаружил Теория относительности.

Код в тексте написан в M-выражениях. Я работаю над переводчиком для перевода из M-выражений в S-выражения (lisp code) @https://github.com/Viruliant/MccarthyMCEval-1.5.

В любом случае здесь приведенная цитата со страницы 13:

;______________________________________Lisp Meta-Circular Evaluator S-Expression
;this code is written in the order it appears on pages 10-13 in the Lisp 1.5 Manual 
;and is translated from the m-expressions into s-expressions

(label mc.equal (lambda (x y)
    (mc.cond
        ((atom x) ((mc.cond ((atom y) (eq x y)) ((quote t) (quote f)))))
        ((equal (car x)(car y)) (equal (cdr x) (cdr y)))
        ((quote t) (quote f)))))

(label mc.subst (lambda (x y z)
    (mc.cond
        ((equal y z) (x))
        ((atom z) (z))
        ((quote t) (cons (subst x y (car z))(subst x y (cdr z)))))))

(label mc.append (lambda (x y)
    (mc.cond 
        ((null x) (y))
        ((quote t) (cons (car x)) (append (cdr x) y)))))

(label mc.member (lambda (x y)
    (mc.cond ((null y) (quote f))
    ((equal x (car y)) (quote t))
    ((quote t) (member x (cdr y))))))

(label mc.pairlis (lambda (x  y  a)
    (mc.cond ((null x) (a)) ((quote t) (cons (cons (car x)(car y))
    (pairlis (cdr x)(cdr y) a)))))

(label mc.assoc (lambda (x a)
    (mc.cond ((equal (caar a) x) (car a)) ((quote t) (assoc x (cdr a))))))

(label mc.sub2 (lambda (a z)
    (mc.cond ((null a) (z)) (((eq (caar a) z)) (cdar a)) ((quote t) (
    sub2 (cdr a) z)))))

(label mc.sublis (lambda (a y)
    (mc.cond ((atom y) (sub2 a y)) ((quote t) (cons (sublis a (car y))))
    (sublis a (cdr y)))))

(label mc.evalquote (lambda (fn x)
    (apply fn x nil)))

(label mc.apply (lambda (fn x a)
    (mc.cond ((atom fn) (
        (mc.cond
            ((eq fn car) (caar x))
            ((eq fn cdr) (cdar x))
            ((eq fn cons) (cons (car x)(cadr x)))
            ((eq fn atom) (atom (car x)))
            ((eq fn eq) (eq (car x)(cadr x)))
            ((quote t) (apply (eval (fn a)x a))))))
        ((eq (car fn) lambda) (eval (caddr fn) (parlis (cadr fn) x a)))
        ((eq (car fn) label) (apply (caddr (fn)x cons (cons (cadr (fn)))
            (caddr fn))a)))))

(label mc.eval (lambda (e a)
    (mc.cond
        ((atom e) (cdr (assoc e a)))
        ((atom (car e)) (mc.cond
            ((eq (car e) quote) (cadr e))
            ((eq (car e) cond) (evcon (cdr e) a))
            ((quote t) (apply (car e) (evlis (cdr e) a) a))))
        ((quote t) (apply (car e) (evlis (cdr e) a) a))))))

(label mc.evcon (lambda (c a)
    (mc.cond 
        ((eval (caar c) a) (eval (cadar c) a))
        ((quote t) (evcon (cdr c) a)))))

(label mc.evlis (lambda (m a)
    (mc.cond
        ((null m) (nil))
        ((quote t) (cons (eval (car m) a) (evlis (cdr m) a)))))))

Ответ 1

Существуют две разные проблемы:

Во-первых: динамическое связывание как ошибка

Не уверен, что он имеет в виду, но, как правило, в McCarthy EVAL использование dynamic binding можно рассматривать как ошибку. Он не использует лексическую область для переменных. Здесь появляется ошибка:

http://www-formal.stanford.edu/jmc/recursive/node3.html

Смотрите функции maplist и diff. Оба используют x. Это не будет работать, как показано, так как ранний Lisp обеспечивает динамическое связывание.

Простейший пример, который показывает, что оценщик использует динамическое связывание

Обратите внимание на использование eval., которое является EVAL от Маккарти.

CL-USER 36 > (eval. '((lambda (f)
                        ((lambda (x) (f))
                         'foo))
                      '(lambda () x))
                    nil)

Это возвращает FOO, так как значение x просматривается с динамической привязки.

Если мы посмотрим на код, нам требуется передать функцию в виде списка: '(lambda () x)). Это оценивает список. Позже список будет вызван через (f) - без аргументов. Затем этот список интерпретируется как лямбда-выражение, а x будет разрешен, посмотрев текущую динамическую привязку для x. Существует привязка x к FOO, введенная ((lambda (x) (f)) 'foo). Это будет использоваться тогда.

В реализации Lisp 1.5 с 60-х годов можно написать нечто похожее на:

((lambda (f)
   ((lambda (x) (f))
    'foo))
 (function (lambda () x)))

Обратите внимание, что (function (lambda () x)) оценивает список маркеров, динамическую среду и функцию. К сожалению, реализация Lisp 1.5 все еще использовала динамическое связывание. Так что это было уже в правильном направлении, но ошибка на самом деле не была исправлена. Улучшена ситуация, когда в качестве аргументов передавались функции другим функциям.

Проблема FUNARG

В 60-е и начале 70-х годов потребовалось довольно много времени, чтобы выяснить эту проблему. Это было известно как проблема FUNARG. См., Например, документ Джоэла Мозеса: Функция FUNCTION в LISP или почему проблема FUNARG должна быть вызвана проблемой окружения. Существуют различные решения для создания закрытий и использования лексической привязки. Часто интерпретатор использовал динамическое связывание, а компилятор использовал лексическую привязку. В мире Lisp это было в основном решено на Схеме, которая по умолчанию вводила лексическое привязку. Это лексическое связывание позволяет, например, использовать замыкания для эмуляции объектных систем (что, вероятно, может найти Кэй). См. Статью: СХЕМА: Переводчик для расширенного исчисления лямбда от 1975 года.

В Common LISP, который использует лексическую область по умолчанию, как схема диалектов Lisp, приведенный выше пример будет ошибкой (здесь мы используем EVAL из Common LISP), слегка изменив код, чтобы сделать это юридический Общий код Lisp):

CL-USER 43 > (eval '((lambda (f)
                       ((lambda (x) (funcall f))
                        'foo))
                     (function (lambda () x))))

Error: The variable X is unbound.

Как вы можете видеть в Common Lisp (и Scheme), (lambda () x) - это реальное лямбда-выражение, а не цитируемый список, а (function (lambda () x)) - объект функции - если есть привязки, то это замыкание - объект функции и ее привязки. Этот объект функции /clojure передается, а затем вызывается через (funcall f). Поскольку x ничего не означает (это свободная переменная) и не просматривается с помощью привязок, возникает ошибка при выполнении кода. Это то, что мы хотим: мы хотим лексической привязки, и эта ошибка в нашем коде является следствием. То, что эта ошибка не происходит в Маккарти, оригинал Lisp является одним из результатов ошибки. Фиксация этой ошибки (которая потребовала больше десятилетия для полного удовлетворения), позволяет нам использовать замыкания в Lisp - как в Common LISP, который узнал ее из Схемы.

Вероятно, Кей также видел динамическое связывание в качестве ошибки. Это очень фундаментальная проблема, ее понимание/решение, помогло разработать и разработать лучшие языки программирования.

Обратите внимание, что типичные ранние реализации Smalltalk (пример Xerox 'Smalltalk 80) также использовали динамическое связывание.

Маккарти об этой ошибке

В От Lisp от 1 до Lisp 1.5 (1979) Маккарти пишет (смело мне):

д. Свободные переменные. Всю невинность Джеймс Р. Слагл запрограммировал следующее определение функции Lisp и жаловался, когда это не сработало правильно:

Объект функции - найти подвыражение x, удовлетворяющее p [x], и вернуть f [x]. Если поиск не увенчался успехом, то функция продолжения u [] без аргументов должна быть вычислена и возвращается значение. Трудность заключалась в том, что при возникновении внутренней рекурсии значение автомобиля [x] было внешним значением, но фактически использовалось внутреннее значение. В современной терминологии было предложено лексическое исследование, и было получено динамическое очертание.

Должен признаться, что я рассматривал эту проблему как просто ошибку и выразил уверенность, что Стив Рассел скоро ее исправит. Он исправился, но, изобретая так называемое FUNARG-устройство, которое взяло лексическую среду вместе с функциональным аргументом. Подобные трудности появились позже в Алголе 60, и Рассел оказался одним из наиболее полных решений проблемы. Несмотря на то, что он хорошо работал в интерпретаторе, в компилированном коде, похоже, противостоят всесторонность и скорость, что приводит к череду компромиссов. К сожалению, время не позволяло написать приложение, дающее историю проблемы, и заинтересованный читатель упоминается (Моисей 1970) как место для начала. (Дэвид Парк говорит мне, что Патрик Фишер также участвовал в разработке устройства FUNARG).

Это не связано со второй проблемой:

Во-вторых: ошибки в другой версии EVAL в той же книге

См. Paul Graham Корни Lisp для обсуждения ошибки в определении EVAL позже в книге. На странице 12 вы найдете описание ошибки в коде Маккарти, которая вызывает двойную оценку аргументов именованной функции.

Ответ 2

Скорее всего, это ссылка на ошибку динамической области (ошибка с результат не делает то, что он должен был делать, если он ожидал аналогично лямбда-исчислению):

eq[car[fn];LAMBDA] -> eval[caddr[fn];pairlis[cadr[fn];x;a]];
                                    %^^^^^^^^^^^^^^^^^^^^^

И в отредактированном полу- lisp -подобном тексте:

((eq (car fn) lambda) (eval (caddr fn) (parlis (cadr fn) x a)))
                                      ;^^^^^^^^^^^^^^^^^^^^^^

Но это совсем не полезно. Исправление должно происходить не только там, в это одно место; и вам нужно понять, как все это работает и действительно следуйте за ним, чтобы узнать, как происходит ошибка.

Как быстрый намек в правильном направлении, проблема здесь в том, что тело лямбда-функции оценивается в среде (вторая аргумент eval), который является расширением текущей среды, что приводит к динамическому охвату. Решение этого вопроса - реализация лексического охвата --- включает в себя не просто редактирование здесь, поскольку информация о лексический объем функции уже потерян.

(Любая случайная порядочная книга по PL должна иметь гораздо больше деталей. контекст Lisp, копание глубже действительно приведет вас к целому FUNARG вещь.)

Ответ 3

он выглядит как

equal[x;y] =
        [atom[x] -> [atom[y] -> eq[x;y]; T -> F];
         equal[car[x];car[y]] -> equal[cdr[x];cdr[y]];
         T -> F]

не обрабатывает случай, когда x является минусом, а y является атомом

edit: также assoc не сможет вернуть нуль, если ключ не найден.

Ответ 4

update: здесь видео Филиппа Вадлера, где он говорит (в 22:30) об этой "ошибке" и о том, как использовать "неправильная переменная" (для среды, которая будет использоваться при оценке лямбда-тела, a вместо env в следующем псевдокоде) приводит к "динамической привязке", а не к "статическому ожиданию".


Я переписал оригинальные M-выражения в некотором псевдокоде ad-hoc, который мне легче следовать, используя : для cons или ., с сопоставлением шаблонов и т.д., которые следует вскоре.

На самом деле, я не вижу проблемы с двойной оценкой. apply ожидает, что его аргументы уже оценены, а eval оценивает их для него.

Я думаю, что ошибка была в оригинальной статье "Рекурсивные функции символических выражений" (обновление: да! Райнер Йосвиг упоминает в конце его ответ на статью Пола Грэма, в которой говорится, что это было в отношении кода в документе).

И действительно, похоже, что замечание Алана Кей было связано с динамическим охватом. Он упоминает, что выглядит "внизу pg 13" и что там, где определено определение evlis, которое оценивает элементы списка в той же самой текущей среде. Посмотрите на страницы страниц 70, 71 для решения этой проблемы, требуя от программиста явно привязать свои определения лямбда к новому ключевому слову function, чтобы создать funarg -tagged списки, упаковывающие лямбды вместе с (или закрывая, как в "закрытии" ) среда в момент оценки формы function, т.е. среда определения этой формы lambda.

Моисей 1970 называет это обязательной средой, обсуждая только неявное создание замыканий при использовании в качестве функциональных аргументов в вызове функции более высокого порядка (следовательно, "funarg" moniker). Это также контекст в более современных дискуссиях о "глубокой и неглубокой привязке" (неправильное использование терминологии, на мой взгляд) в слове Perl и т.д.

Но, глядя на эту расширенную версию, похоже, что это позволит программисту создавать такие объекты явно, хранить их в переменной, передавать ее как только любой другой объект первого класса. Затем, когда применяется замыкание (a funarg -tagged list), определение лямбда оценивается в его среде, а не в текущей (то, что Moses 1970 вызывает среду активации). Это очень близко к убедительному подражанию лексическому охвату с динамической привязкой.

Здесь этот псевдокод:

evalquote fn x = apply fn x []     %% `x` a list of arguments

apply CAR ((x:_):_) a = x          %% `a` an association-list, the environment
 |    CDR ((_:x):_) a = x
 |   CONS (x:y:_)   a = x:y
 |   ATOM (x:_)     a = atom x
 |     EQ (x:y:_)   a = eq x y
 | fn xs a , atom fn       = apply (eval fn a) xs a
 | (LAMBDA args body) xs a = eval body (pairlis args xs a)
 | (LABEL fn lambda)  xs a = apply lambda xs ((fn:lambda):a)
 %% and the FUNARG device, pg 70:
 | (FUNARG lambda env) xs a = apply lambda xs env            % not a, NB!

eval e a , atom e      = assv e a
 | (QUOTE e)   _       = e
 | (COND (t c) : cs) a = { eval t a -> eval c a ; eval (COND:cs) a }
 %% the FUNARG device, pg 71:
 | (FUNCTION lambda) a = (FUNARG lambda a) % store the lambda definitional environment!
 | (e1:es) _ , atom e1 = apply e1 (evlis es a) a   % or shortcut this directly to: 
                         % apply (eval e1 a) (evlis es a) a
 | (e1:es) _           = apply e1 (evlis es a) a  

evlis (m : ms) a           = eval m a : evlis ms  | [] _ = []
equal (x:xs) (y:ys)        = equal x y && equal xs ys
 |    x y , atom x, atom y = eq x y               | _ _  = F
subst x y z , equal y z = x
 |    _ _ z , atom z    = z
 |    x y (h:t)         = subst x y h : subst x y t
append (x:xs) ys        = x : append xs ys        | [] ys = ys
member x (y:_) , equal x y = T 
 |     x (_:ys)         = member x ys             | _ []  = F
pairlis (x:xs) (y:ys) a = (x:y) : pairlis xs ys a | _ _ a = a
assv x ((h:y):ys)       = { x==h -> y ; assv x ys }
sub2 ((x:v):xs) z   = { eq x z -> v ; sub2 xs z } | [] z  = z
sublis a y , atom y = sub 2 a y
 | a (y:ys)         = sublis a y : sublis a ys