Перетворити λ-вирази в SK-вирази


20

Λ-числення , або лямбда - обчислення, є логічною системою , заснованої на анонімних функціях. Наприклад, це λ-вираз:

λ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


1
Я думаю, що ваш другий тестовий випадок невірний. Останній містить число, не в дужках.
Крістіан Сіверс


Як ви потрапили λ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), правда?
BarbaraKwarc

@BarbaraKwarc Право, я мав на увазі SII, ні SKK. Це була помилка.
Esolanging Fruit

Відповіді:


9

Haskell, 251 237 222 214 байт

15 байт збережено завдяки @ Ørjan_Johansen (також дивіться його посилання TIO у зауваженнях)!

Ще 8 байтів збережено завдяки @nimi!

data E=S|K|E:>E|V Int
p(h:s)|h>'_',(u,a)<-p s,(v,b)<-p u=(v,a:>b)|h>'['=a<$>p s|[(n,_:t)]<-reads s=(t,V n)
a(e:>f)=S:>a e:>a f
a(V 0)=S:>K:>K
a(V n)=K:>V(n-1)
a x=K:>x
o(e:>f)='`':o e++o f
o S="S"
o K="K"
f=o.snd.p

pаналізує вхід, повертаючи залишилася нерозбірливу частину в першому компоненті отриманої пари. Першим символом його аргументу повинен бути зворотний відбір, зворотна косою рискою або початкова дужка. Охоронці шаблону pперевіряють ці випадки в цьому порядку. У першому випадку, позначаючи додаток, ще два вирази розбираються та об'єднуються до елемента типу Eданих із конструктором інфіксації :>. У випадку лямбда наступний вираз аналізується і негайно надається aфункції. В іншому випадку це змінна, ми отримуємо її номер за допомогою readsфункції (яка повертає список) і відкидаємо закриваючу дужку за відповідності шаблону (_:t).

Ця aфункція виконує досить відому дужку абстракції. Щоб абстрагувати додаток, ми абстрагуємо два підвираження та використовуємо Sкомбінатор для розподілу аргументу обом. Це завжди правильно, але, маючи більше коду, ми могли б зробити набагато краще , обробляючи спеціальні випадки, щоб отримати короткі вирази. Абстрагуючись поточним змінним даєш Iабо, коли ми не маємо цього SKK. Зазвичай решта випадків можуть просто додати Kпередню частину, але при використанні цього позначення нам доводиться перенумеровувати змінні, оскільки внутрішня лямбда абстрагується.

oперетворює результат у рядок для виведення. fє повною функцією.

Як і в багатьох мовах, зворотна косої риси є символом втечі, тому її потрібно задавати двічі в рядковому буквальному:

*Main> f "\\[0]"
"``SKK"
*Main> f "\\`[0][0]"
"``S``SKK``SKK"
*Main> f "\\\\[0]"
"``S``S`KS`KK`KK"
*Main> f "\\\\[1]"
"``S`KK``SKK"
*Main> f "\\\\\\``[2][0]`[1][0]"
"``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S`KK`KK``S`KK``SKK``S``S`KS``S``S`KS``S`KK`KS``S`KK`KK``S`KK`KK``S``S`KS``S``S`KS``S`KK`KS``S``S`KS``S`KK`KK``S``S`KS`KK`KK``S``S`KS``S``S`KS``S`KK`KS``S`KK`KK``S`KK`KK"

1
1. На другому рядку ви можете використовувати (a,(b,v))<-p<$>p s. 2. Це '\\'може бути справедливим, _якщо ви перенесите цей матч останнім.
Ørjan Johansen

1
Насправді, подряпайте першу частину: коротше поміняти порядок кортежу та використовувати p(_:s)=a<$>p sдля (переміщеної) '\\'лінії.
Ørjan Johansen

1
Спробуйте в Інтернеті! для вашої поточної версії. Що до речі лише 236 байт, ви можете залишити остаточний рядок.
Ørjan Johansen

2
@ Challenger5 Я думаю, що це в основному пов'язано з тим, що haskell заснований на обчисленні лямбда, тому люди, які знають хескелл, частіше залучаються до подібного роду питань :)
Лев,

2
Ви можете визначити pза допомогою одного вираження з трьома охоронцями, переставити випадки і падіння зайвої пари (): p(h:s)|h>'_',(u,a)<-p s,(v,b)<-p u=(v,a:>b)|h>'['=a<$>p s|[(n,_:t)]<-reads s=(t,V n).
німі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.