Конвертировать λ-выражения в 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
  • Это самая сложная замена. Замените каждую переменную числом в скобках в зависимости от того, насколько глубоко вложенная переменная относится к лямбда-заголовку, к которому она принадлежит (т. Е. Используйте индексацию по Де-Брюину на основе 0 ). Например, в \xx(функция идентификации) xтело в теле будет заменено на [0], поскольку оно принадлежит первому (основанному на 0) заголовку, встречающемуся при прохождении выражения от переменной до конца; \x\y``\x`xxxyбудет преобразован в \x\y``\x`[0][0][1][0]. Теперь мы можем отбросить переменные в заголовках, оставив \\``\`[0][0][1][0].

Комбинаторная логика - это, по сути, тарпит Тьюринга, составленный из λ-исчисления (ну, на самом деле, он появился первым, но здесь это не имеет значения.)

«Комбинаторную логику можно рассматривать как вариант лямбда-исчисления, в котором лямбда-выражения (представляющие функциональную абстракцию) заменяются ограниченным набором комбинаторов, примитивных функций, из которых отсутствуют связанные переменные». 1

Наиболее распространенным типом комбинаторной логики является комбинаторное исчисление SK , в котором используются следующие примитивы:

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данных с помощью конструктора infix :>. В лямбда-случае следующее выражение анализируется и сразу передается aфункции. В противном случае это переменная, мы получаем ее номер с помощью readsфункции (которая возвращает список) и удаляем закрывающую скобку при сопоставлении с шаблоном (_:t).

aФункция делает достаточно хорошо известный кронштейн абстракции. Чтобы абстрагировать приложение, мы абстрагируем два подвыражения и используем Sкомбинатор для распределения аргумента между ними. Это всегда правильно, но с большим количеством кода мы могли бы сделать намного лучше , обрабатывая особые случаи, чтобы получить более короткие выражения. Абстрагируясь текущим переменным даешь Iили, когда мы не имеем этого SKK. Обычно в остальных случаях можно просто добавить a Kв начало, но при использовании этой записи мы должны перенумеровать переменные, поскольку внутренняя лямбда абстрагируется.

oпревращает результат в строку для вывода. fэто полная функция.

Как и во многих языках, обратная косая черта является escape-символом, поэтому ее нужно указывать дважды в строковом литерале:

*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. '\\'Может быть, только _если вы передвинете этот матч последним.
Орджан Йохансен

1
На самом деле, вычеркните первую часть: короче поменять порядок кортежей и использовать p(_:s)=a<$>p sдля (перемещенной) '\\'строки.
Орджан Йохансен

1
Попробуйте онлайн! для вашей текущей версии. Что, кстати, всего 236 байт, вы можете сбросить последний перевод строки.
Орджан Йохансен,

2
@ Challenger5 Я думаю, что это в основном из-за того, что haskell основан на лямбда-исчислении, поэтому люди, разбирающиеся в 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.