(self self) вызов внутри оператора let на строгом языке
В настоящее время я просматриваю эту статью о Y-комбинаторе Майка Ванье.
По пути вывода Y-комбинатора этот код:
(define (part-factorial self)
(lambda (n)
(if (= n 0)
1
(* n ((self self) (- n 1))))))
((part-factorial part-factorial) 5) ==> 120
(define factorial (part-factorial part-factorial))
(factorial 5) ==> 120
разработан для:
(define (part-factorial self)
(let ((f (self self)))
(lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
(define factorial (part-factorial part-factorial))
(factorial 5) ==> 120
После этого в статье говорится:
Это будет хорошо работать на ленивом языке. На строгом языке
(self self)
вызов в операторе let отправит нас в бесконечный цикл, потому что для того, чтобы вычислить(part-factorial part-factorial)
(в определении факториала) сначала нужно будет рассчитать (частично-факториал частично-факториал) (вlet
выражение).
и тогда читатель бросает вызов:
Для развлечения: выясните, почему это не было проблемой с предыдущим определением.
Мне кажется, я понял, почему, хотя я хотел бы подтвердить, что:
- Я прав в моем понимании.
- В моем понимании я не пропускаю никаких критических моментов.
Насколько я понимаю, в первом фрагменте кода (self self)
вызов не приведет к бесконечному циклу, потому что он содержится (обернут) в lambda
как part-factorial
функция, и, таким образом, оценивается lambda (n)
до вызова (self self)
на самом деле сделано, что происходит только для n > 0
, Таким образом, после (= n 0)
оценивает #t
нет необходимости звонить (self self)
,
2 ответа
Да, "дай через лямбду" во втором определении
(define (part-factorial self)
(let ((f (self self))) ; let above the
(lambda (n) ; lambda
(if (= n 0)
1
(* n (f (- n 1)))))))
вызывает применение (self self)
быть запущенным до (lambda (n) ...)
могут быть возвращены.
Это предлагает другой способ избежать зацикливания: поставить само проблемное самоприменение позади собственного lambda
:
(define (part-factorial self)
(let ((f (lambda (a) ((self self) a))))
(lambda (n)
(if (= n 0)
1
(* n (f (- n 1)))))))
и теперь это тоже работает. Он известен как "эта-экспансия" (или "эта-конверсия" в целом, так как его двойник - "эта-сжатие").
Этот способ фактически используется в обычном определении Y-комбинатора аппликативного порядка.
В первом определении (self self)
Приложение запускается только тогда, когда его результат действительно необходим - но цена его заключается в том, что нам пришлось писать его в несколько "неестественном" стиле, который в любом случае отличается от того, что мы хотели бы написать, т.е. просто использовать f
ссылаться на функцию, которая каким-то образом сделана для нас закулисной.
С явным самоприменением бремя лежит на нас, и мы, люди, знаем, что ошибаемся. Ведь человеку свойственно ошибаться, а прощать - Божественному; но наши компьютеры еще не на этой всепрощающей стадии.
Так вот почему Y. Позвольте нам написать это прямо, без забот, с деталями, выверенными и отобранными.
И бремя явного самоприменения не должно упоминаться.
Да, это правильный ответ. И действительно, этот трюк (завершение чего-то, что в противном случае повторялось бы в лямбде) является критическим при определении Y для языков аппликативного порядка, о которых, как мне кажется, говорится в его статье (кстати, это хорошая статья).