Нерекурсивная факторная функция лямбда-исчисления

Как написать факториальную функцию без использования рекурсии с использованием лямбда-исчисления? Это означает только математическую запись, а не реализацию в каком-либо конкретном языке программирования.

2 ответа

Решение

Если под "без использования рекурсии" вы имеете в виду без общей рекурсии и, следовательно, без фиксированных точек (или собственных приложений), мы можем просто наблюдать, что факториальная функция является примитивно-рекурсивной (то есть итеративной, по сути), и существует очень общее и простое кодирование примитивной рекурсии с помощью итерации (предоставленной церковными цифрами) и пар. Я буду обсуждать общий случай, который весьма поучителен. Пусть - некоторое кодирование пар, а fst и snd - связанные проекции. Например, вы можете определить

<M,N> = λz. z M N
fst = λp. p (λxy.x)
snd = λp. p (λxy.y)

Предположим, что у нас есть примитивно-рекурсивное определение (без параметров, для простоты)

f(0) = a
f(x+1) = h(x,f(x))

где а и ч уже определены.

Общая идея состоит в том, чтобы использовать вспомогательную функцию f', где

                       f'(x) = <x,f(x)>

Итак, f'(0) = < 0, a>, и, учитывая пару p = = f'(x), мы строим следующую пару путем вычисления преемника в первом компоненте и применения h к паре аргументов (что, используя наше кодирование пар, просто равнозначно передать продолжение h в качестве входных данных для пары p).

Подводя итог,

next = λp.< succ (fst p), p h>

Наконец, чтобы вычислить f(n), нам нужно итерировать следующую функцию n раз, начиная с <0, a>, а затем взять второй компонент:

 f = λn. snd (n next <0,a>)

я ничего не сказал, я не имел в виду

Под "без использования рекурсии" вы должны понимать "без функции, вызывающей себя по имени "

В любом случае, давайте напишем факториал

fact := λn. zero? n one (mult n (fact (dec n)))

Теперь нас не особо волнует zero?, mult, dec, или даже one в этом примере; Вы можете реализовать их по своему усмотрению - мы просто хотим убрать выделенную жирным шрифтом рекурсию по имени,

... fact (dec n)

Самый простой способ сделать это - применить лямбду к себе - познакомьтесь с комбинатором U

U := λf. f f

Теперь мы можем обернуть наше оригинальное выражение в лямбду и применить U

fact := U (λf. λn. zero? n one (mult n (??? (dec n))))

Но что мы ставим на место fact??? - Напомним, что f является ссылкой на саму внешнюю лямбду, поэтому для того, чтобы это было тем же случаем в следующей "итерации", мы должны применить f сам по себе, как и U - на самом деле, вы можете думать о U как о некоем зеркале, и ваша функция может отражаться обратно в это зеркало для повторения; только на этот раз, без использования имени ^_^

fact := U (λf. λn. zero? n one (mult n (f f (dec n))))

Да, да, но еще более проницательный заметит, что вы можете использовать зеркало (U) прямо внутри лямбды.

fact := U (λf. λn. zero? n one (mult n (U f (dec n))))

расширен без U

Мы знаем, как расширить внутреннее - мы написали это так в первый раз

fact := U (λf. λn. zero? n one (mult n (f f (dec n))))

Теперь разверните внешний U

fact := (λf. λn. zero? n one (mult n (f f (dec n)))) (λf. λn. zero? n one (mult n (f f (dec n))))

это работает?

все церковные цифры представлены как #N

fact := U λf. λn. zero? n #1 (mult n (U f (dec n)))

fact #4

U (λf. λn. zero? n #1 (mult n (U f (dec n))) #4

(λf. f f) (λf. λn. zero? n #1 (mult n (U f (dec n))) #4

(λf. λn. zero? n #1 (mult n (U f (dec n))) (λf. λn. zero? n #1 (mult n (U f (dec n))) #4

(λn. zero? n #1 (mult n (U (λf. λn. zero? n #1 (mult n (U f (dec n))) (dec n))) #4

zero? #4 #1 (mult #4 (U (λf. λn. zero? n #1 (mult n (U f (dec n))) (dec #4)))

zero? #4 #1 (mult #4 (U (λf. λn. zero? n #1 (mult n (U f (dec n))) (dec #4)))

// (zero? #4); false; returns second argument
(mult #4 (U (λf. λn. zero? n #1 (mult n (U f (dec n))) (dec #4)))

// which is #4 * ...
(U (λf. λn. zero? n #1 (mult n (U f (dec n))) (dec #4))

// which is ...
(U (λf. λn. zero? n #1 (mult n (U f (dec n))) #3)

// which is equivalent to...
fact #3

// so ...
(mult #4 (fact #3))

// repeating this pattern ...
(mult #4 (mult #3 (fact #2))
(mult #4 (mult #3 (mult #2 (fact #1)))
(mult #4 (mult #3 (mult #2 (mult #1 (fact #0))))
(mult #4 (mult #3 (mult #2 (mult #1 #1))))
(mult #4 (mult #3 (mult #2 #1)))
(mult #4 (mult #3 #2))
(mult #4 #6)
#24

демонстрация в JavaScript

Идите вперед и посмотрите на мощь U комбинатора своими собственными глазными яблоками!

const U =
  f => f (f)
  
const fact =
  U (f => n =>
    n === 0 ? 1 : n * U (f) (n - 1))
    
console.log (fact (4)) // 24

И снова как чистое лямбда-выражение

console.log (
  (f => n => n === 0 ? 1 : n * f (f) (n - 1))
    (f => n => n === 0 ? 1 : n * f (f) (n - 1))
      (4)
) // 24

взаимная рекурсия

Приведенный выше фрагмент демонстрирует очень важное свойство этого рекурсивного процесса: он является взаимно рекурсивным. Как видите, на самом деле есть две (хотя и одинаковые) функции, управляющие рекурсией; A вызывает B, B вызывает A - Однако в случае U-комбинатора есть только одна функция, которая применяется к себе, так что она фактически разрешает прямую рекурсию - лямбда вызывает сам себя, используя свой параметр, а не имя (у лямбд нет имени для звонка)


Y?

Потому что я так сказал

U-комбинатор немного громоздок, потому что он ожидает, что пользователь "отразит" функцию, чтобы повторить - что если бы мы могли снабдить внешнюю лямбду функцией, которая является самим зеркалом?

U := λf. f f
Y := λg. U (λf. g (U f))

Я снова покажу вам то же определение, но в двух строках, чтобы вы могли видеть "зеркала" и их положение.

          / g, user's function
         /
        /  / outer reflection
       /  /
Y := λg. U (λf.   ...   )
                g (U f)
                 \
                  \ call g with inner reflection

Так что теперь, когда вы подаете заявление Y для любой лямбды (g) ее параметр становится функцией для вычисления самой лямбды - изменение fact в

fact := Y (λf. λn. zero? n one (mult n (f (dec n))))

расширяя Y

Y := λg. U (λf. g (U f))             // expand inner U
Y := λg. U (λf. g (f f))             // expand outer U
Y := λg. (λf. g (f f)) (λf. g (f f))

какое определение вы видите в википедии; просто переименованный


у меня только что было глубокое открытие

Получив Y из U выше, я увидел нечто, чего я никогда раньше не видел - скрытый B

Y := λg. U (λf. g (U f))

B := λf. λg. λx. f (g x)

Y' := λg. U (B g U)

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

#lang lazy

(define B (λ (f)
            (λ (g)
              (λ (x)
                (f (g x))))))

(define U (λ (f)
            (f f)))

(define Y (λ (g)
            (U ((B g) U))))

(define fact (Y (λ (f)
                  (λ (n)
                    (if (= n 0)
                        1
                        (* n (f (sub1 n))))))))

(fact 4) ;; 24

Haskellers

Вы когда-нибудь видели Y, выраженную как?

Y = U . (. U)
  where U f = f f
Другие вопросы по тегам