(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 выражение).

и тогда читатель бросает вызов:

Для развлечения: выясните, почему это не было проблемой с предыдущим определением.

Мне кажется, я понял, почему, хотя я хотел бы подтвердить, что:

  1. Я прав в моем понимании.
  2. В моем понимании я не пропускаю никаких критических моментов.

Насколько я понимаю, в первом фрагменте кода (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 для языков аппликативного порядка, о которых, как мне кажется, говорится в его статье (кстати, это хорошая статья).

Другие вопросы по тегам