将λ表达式转换为SK表达式


20

所述λ演算,或演算,是根据匿名功能的逻辑系统。例如,这是一个λ表达式:

λf.(λx.xx)(λx.f(xx))

但是,出于此挑战的目的,我们将简化表示法:

  • 更改λ\(以便于键入):\f.(\x.xx)(\x.f(xx))
  • .拉姆达头是不必要的,所以我们可以删除它:\f(\xxx)(\xf(xx))
  • 在应用程序中使用Unlambda样式的前缀表示法,`而不是将两个函数一起编写(有关如何执行此操作的完整说明,请参见在Lambda微积分表示法之间转换):\f`\x`xx\x`f`xx
  • 这是最复杂的替换。根据变量相对于其所属的lambda标头的嵌套深度(即,使用基于0的De Bruijn索引),用括号中的数字替换每个变量。例如,在\xx(身份函数)中,x主体中的in将替换为[0],因为它属于在将表达式从变量遍历到末尾时遇到的第一个(从0开始)头。\x\y``\x`xxxy将被转换为\x\y``\x`[0][0][1][0]。现在,我们可以将变量放在标头中,离开\\``\`[0][0][1][0]

组合逻辑基本上是由λ微积分构成的Turing Tarpit(嗯,实际上,它是第一位的,但是在这里无关紧要。)

“组合逻辑可以看作是lambda演算的一种变体,其中lambda表达式(代表功能抽象)被有限的组合器集合所取代,这些组合器不存在绑定变量。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
我认为您的第二个测试用例不正确。最后一个数字不包含在方括号中。
Christian Sievers


你是怎么得到的λ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 '18

Answers:


9

Haskell,251237222214字节

@Ørjan_Johansen节省了15个字节(另请参见备注中的TIO链接)!

多亏@nimi,节省了8个字节!

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构造函数解析另外两个表达式并将其组合为数据类型的元素:>。在lambda情况下,以下表达式将被解析并立即提供给a函数。否则,它是一个变量,我们可以通过reads函数(返回列表)获得其编号,并通过与模式匹配将右括号删除(_:t)

a函数执行众所周知的括号抽象。为了抽象化一个应用程序,我们抽象了这两个子表达式,并使用S组合器将参数分配给这两个子表达式。这总是正确的,但是使用更多的代码,我们可以通过处理特殊情况来获得更好的表达式,从而做得更好。将当前变量抽象为I或者,如果没有的话,则为SKK。通常情况下,其余情况只能K在前面加上a ,但是当使用这种表示法时,由于抽象了内部lambda,我们必须对变量重新编号。

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. '\\'可以只是_您最后移动该匹配项。
与Orjan约翰森

1
实际上,请抓紧第一部分:交换元组顺序并p(_:s)=a<$>p s用于(移动的)'\\'行要短一些。
与Orjan约翰森

1
在线尝试!您当前的版本。顺便说一下,它只有236个字节,您可以删除最后的换行符。
与Orjan约翰森

2
@ Challenger5我认为这主要是由于haskell基于lambda演算,所以精通haskell的人更容易被此类问题吸引:)
Leo

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)
nimi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.