Реализация последовательности Фибоначчи с использованием чистого лямбда-исчисления и церковных чисел в Racket - PullRequest
0 голосов
/ 28 сентября 2018

Я уже давно борюсь с лямбда-исчислением.Существует множество ресурсов, которые объясняют, как уменьшить вложенные лямбда-выражения, но меньше, чтобы они помогли мне написать свои собственные лямбда-выражения.

Я пытаюсь написать рекурсивное решение Фибоначчи в Racket, используя чистое лямбда-исчисление(то есть функции с одним аргументом, церковные цифры).

Это определения церковных цифр, которые я использовал:

(define zero  (λ (f) (λ (x) x)))
(define one   (λ (f) (λ (x) (f x))))
(define two   (λ (f) (λ (x) (f (f x)))))
(define three (λ (f) (λ (x) (f (f (f x))))))
(define four  (λ (f) (λ (x) (f (f (f (f x)))))))
(define five  (λ (f) (λ (x) (f (f (f (f (f x))))))))
(define six   (λ (f) (λ (x) (f (f (f (f (f (f x)))))))))
(define seven (λ (f) (λ (x) (f (f (f (f (f (f (f x))))))))))
(define eight (λ (f) (λ (x) (f (f (f (f (f (f (f (f x)))))))))))
(define nine  (λ (f) (λ (x) (f (f (f (f (f (f (f (f (f x))))))))))))

А это функции с одним аргументом, которые я использовалпытался включить:

(define succ   (λ (n) (λ (f) (λ (x) (f ((n f) x))))))
(define plus   (λ (n) (λ (m) ((m succ) n))))
(define mult   (λ (n) (λ (m) ((m (plus n)) zero))))
(define TRUE   (λ (t) (λ (f) t)))
(define FALSE  (λ (t) (λ (f) f)))
(define COND   (λ (c) (λ (x) (λ (y) ((c x) y)))))
(define iszero (λ (x) (x ((λ (y) FALSE) TRUE))))
(define pair   (λ (m) (λ (n) (λ (b) (((IF b) m) n)))))
(define fst    (λ (p) (p TRUE)))
(define snd    (λ (p) (p FALSE)))
(define pzero  ((pair zero) zero))
(define psucc  (λ (n) ((pair (snd n)) (succ (snd n)))))
(define pred   (λ (n) (λ (f) (λ (x) (((n (λ (g) (λ (h) (h (g f))))) (λ (u) x))(λ (u) u))))))
(define sub    (λ (m) (λ (n) ((n pred) m))))
(define leq    (λ (m) (λ (n) (iszero ((sub m) n))))) ;; less than or equal
(define Y      ((λ (f) (f f)) (λ (z) (λ (f) (f (λ (x) (((z z) f) x))))))) ;; Y combinator

Я начал с написания рекурсивного Фибоначчи в Ракете:

(define (fib depth)
  (if (> depth 1)
    (+ (fib (- depth 1)) (fib (- depth 2)))
  depth))

Но во многих моих попытках мне не удалось написать его с использованием чистого лямбда-исчисления,Даже начало было нелегкой задачей.

(define fib
   (λ (x) ((leq x) one)))

Который я называю (например):

(((fib three) add1) 0)

По крайней мере, это работает (правильно возвращает ноль или единицу Церкви), но добавление чего-либо кроме этого ломает все.

Я очень неопытен с Ракеткой, и Лямбда-исчисление - довольно популярная вещь, как кто-то, кто никогда не поднимал это до недавнего времени.

Я быхотел бы понять, как построить эту функцию, и включить рекурсию с Y комбинатором.Я был бы особенно признателен за объяснение рядом с любым кодом.Достаточно заставить его работать с fib(zero) до fib(six), так как я могу беспокоиться о дальнейшем расширении определений Церкви.


РЕДАКТИРОВАТЬ:

My iszero функция была скрытым диверсантом в моей реализации.Вот правильная версия с обновленными логическими значениями из ответа Алекса:

(define iszero (λ (x) ((x (λ (y) FALSE)) TRUE)))
(define TRUE   (λ (t) (λ (f) (t))))
(define FALSE  (λ (t) (λ (f) (f))))

С этими изменениями и включением thunks все работает как надо!

1 Ответ

0 голосов
/ 28 сентября 2018

Разветвление форм и короткое замыкание

Если вы говорите на нетерпеливом (не ленивом) языке, таком как Racket, вам нужно быть осторожным в том, как вы кодируете формы ветвления, такие как COND.

Существуют следующие определения логических и условных выражений:

(define TRUE   (λ (t) (λ (f) t)))
(define FALSE  (λ (t) (λ (f) f)))
(define COND   (λ (c) (λ (x) (λ (y) ((c x) y)))))

И они работают для простых случаев, таких как:

> (((COND TRUE) "yes") "no")
"yes"
> (((COND FALSE) "yes") "no")
"no"

Однако, если "ветвь не занято" будетвыдает ошибку или бесконечный цикл, тогда хорошая форма ветвления будет "замыкать", чтобы избежать ее запуска.Хорошая форма ветвления должна оценивать только ту ветвь, которая необходима.

> (if #true "yes" (error "shouldn't get here"))
"yes"
> (if #false (error "shouldn't trigger this either") "no")
"no"

Однако ваш COND оценивает обе ветви просто потому, что Приложение функции Racket оценивает все аргументы:

> (((COND TRUE) "yes") (error "shouldn't get here"))
;shouldn't get here
> (((COND FALSE) (error "shouldn't trigger this either")) "no")
;shouldn't trigger this either

Использование дополнительных лямбд для реализации короткихсхема

Способ, которым меня учили обходить это на нетерпеливом языке (например, без переключения на #lang lazy), заключался в том, чтобы передавать thunks таким ветвящимся формам, как это:

(((COND TRUE) (λ () "yes")) (λ () (error "doesn't get here")))

Однако, это требует некоторых небольших изменений в определении того, что такое логическое значение.Ранее логическое значение принимало два значения на выбор и возвращало одно.Теперь логическое значение будет принимать два thunks на выбор, и будет call one.

(define TRUE (λ (t) (λ (f) (t))))   ; note the extra parens in the body
(define FALSE (λ (t) (λ (f) (f))))  ; same extra parens

Форма COND может быть определена одинаковокак и раньше, но вам придется использовать его по-другому.Чтобы перевести (if c t e), где раньше вы писали:

(((COND c) t) e)

Теперь с новым определением логических выражений вы напишите:

(((COND c) (λ () t)) (λ () e))

Я собираюсь сократить (λ () expr) как{expr} так что я могу написать это вместо этого:

(((COND c) {t}) {e})

Теперь, что ранее не удалось с ошибкой, возвращает правильный результат:

> (((COND TRUE) {"yes"}) {(error "shouldn't get here")})
"yes"

Это позволяет вам писать условные выражениягде одна из ветвей представляет собой «базовый случай», где она останавливается, а другая ветвь представляет собой «рекурсивный случай», в котором она будет продолжать работать.

(Y (λ (fib)
     (λ (x)
       (((COND ((leq x) one))
         {x})
        {... (fib (sub x two)) ...}))))

Без этих дополнительных (λ () ....) иновое определение логических значений, это будет цикл за бесконечность из-за энергичной (не ленивой) оценки аргументов Ракета.

...