Λ-числення , або лямбда - обчислення, є логічною системою , заснованої на анонімних функціях. Наприклад, це λ-вираз:
λf.(λx.xx)(λx.f(xx))
Однак для цієї задачі ми спростимо позначення:
- Змініть
λ
на\
(щоб спростити введення):\f.(\x.xx)(\x.f(xx))
- В
.
лямбда-заголовках непотрібно, тож ми можемо його відпустити:\f(\xxx)(\xf(xx))
- Використовуйте позначення префікса Unlambda -style з
`
для застосування, а не писати обидві функції разом (для повного пояснення, як це зробити, див. Перетворити між нотаціями обчислення лямбда ):\f`\x`xx\x`f`xx
- Це найскладніша заміна. Замініть кожну змінну цифрою в дужках, виходячи з того, наскільки глибоко вкладена змінна відносно заголовка лямбда, до якого вона належить (тобто використовуйте індексацію De Bruijn на основі 0 ). Наприклад, у
\xx
(функція ідентичності)x
в тілі буде замінено[0]
, оскільки воно належить до першого (на основі 0) заголовка, що виникає при переході виразу від змінної до кінця;\x\y``\x`xxxy
буде перетворений на\x\y``\x`[0][0][1][0]
. Тепер ми можемо скидати змінні в заголовки, залишаючи\\``\`[0][0][1][0]
.
Комбінаційна логіка - це в основному Тюрінг Тарпіт, зроблений з λ-обчислення (Ну, насправді, він прийшов першим, але це не має значення.)
"Комбінаційну логіку можна розглядати як варіант обчислення лямбда, в якому лямбда-вирази (що представляють функціональну абстракцію) замінюються обмеженим набором комбінаторів, примітивних функцій, від яких пов'язані змінні відсутні." 1
Найпоширенішим типом комбінаторної логіки є обчислення комбінатора СК , яке використовує такі примітиви:
K = λx.λy.x
S = λx.λy.λz.xz(yz)
Іноді I = λx.x
додається комбінатор , але він є зайвим, як SKK
(або взагалі SKx
для будь-якого x
) еквівалентний I
.
Все, що вам потрібно, це K, S та додаток, щоб мати можливість кодувати будь-який вираз у λ-обчисленні. Як приклад, ось переклад від функції λf.(λx.xx)(λx.f(xx))
до комбінаторної логіки:
λf.(λx.xx)(λx.f(xx)) = S(K(λx.xx))(λf.λx.f(xx))
λx.f(xx) = S(Kf)(S(SKK)(SKK))
λf.λx.f(xx) = λf.S(Kf)(S(SKK)(SKK))
λf.S(Sf)(S(SKK)(SKK)) = S(λf.S(Sf))(K(S(SKK)(SKK)))
λf.S(Sf) = S(KS)S
λf.λx.f(xx) = S(S(KS)S)(K(S(SKK)(SKK)))
λx.xx = S(SKK)(SKK)
λf.(λx.xx)(λx.f(xx)) = S(K(S(SKK)(SKK)))(S(S(KS)S)(K(S(SKK)(SKK))))
Оскільки ми використовуємо позначення префікса, це так ```S`K``S``SKK``SKK``S``S`KSS`K``SKK`
.
1 Джерело: Вікіпедія
Змагання
На даний момент ви, напевно, здогадалися, що таке: Напишіть програму, яка приймає дійсне λ-вираження (у позначенні, описаному вище), як вхід і виводить (або повертає) ту саму функцію, переписану в розрахунку SK-комбінатора. Зауважте, що існує нескінченна кількість способів переписати це; вам потрібно вивести лише один із нескінченних способів.
Це код-гольф , тому виграє найкоротше дійсне подання (вимірюється в байтах).
Випробування
Кожен тестовий випадок показує один можливий вихід. Вираз зверху - еквівалентний вираз λ-числення.
λx.x:
\[0] -> ``SKK
λx.xx:
\`[0][0] -> ```SKK``SKK
λx.λy.y:
\\[0] -> `SK
λx.λy.x:
\\[1] -> K
λx.λy.λz.xz(yz):
\\\``[2][0]`[1][0] -> S
λw.w(λx.λy.λz.xz(yz))(λx.λy.x):
\``[0]\\[1]\\\``[2][0]`[1][0] -> ``S``SI`KS`KK
λx.f(xx) = S(Kf)(SKK)
? Чи не повинно бути так λx.f(xx) = S(Kf)(SII) = S(Kf)(S(SKK)(SKK))
? Під час перетворення λx.f(xx)
я отримую, S {λx.f} {λx.xx}
що зводиться до, S (Kf) {λx.xx}
а вираз у дужках - це не що інше, як ω=λx.xx
, як ми знаємо, представлений як SII = S(SKK)(SKK)
, правда?
SII
, ні SKK
. Це була помилка.