Wandle λ-Ausdrücke in SK-Ausdrücke um


20

Der λ-Kalkül oder Lambda-Kalkül ist ein logisches System, das auf anonymen Funktionen basiert. Zum Beispiel ist dies ein λ-Ausdruck:

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

Für diese Herausforderung vereinfachen wir jedoch die Notation:

  • Ändern Sie λzu \(um die Eingabe zu vereinfachen):\f.(\x.xx)(\x.f(xx))
  • Die .In-Lambda-Header sind nicht erforderlich, daher können wir sie löschen:\f(\xxx)(\xf(xx))
  • Verwenden Sie die unlambda -Stil Präfixnotation mit `für die Anwendung , anstatt die beiden Funktionen zusammen zu schreiben (für eine vollständige Erklärung, wie dies zu tun, siehe Konvertieren zwischen Lambda - Kalkül Notationen ):\f`\x`xx\x`f`xx
  • Dies ist die komplizierteste Substitution. Ersetzen Sie jede Variable durch eine Zahl in Klammern, je nachdem, wie tief die Variable im Verhältnis zum Lambda-Header verschachtelt ist, zu dem sie gehört (dh verwenden Sie die 0-basierte De Bruijn-Indizierung ). Zum Beispiel würde in \xx(der Identitätsfunktion) xder Text im Text durch ersetzt [0], da er zum ersten (0-basierten) Header gehört, der beim Durchlaufen des Ausdrucks von der Variablen bis zum Ende angetroffen wird. \x\y``\x`xxxywürde umgewandelt werden in \x\y``\x`[0][0][1][0]. Wir können nun die Variablen in den Headern ablegen und verlassen \\``\`[0][0][1][0].

Die kombinatorische Logik ist im Grunde ein Turing Tarpit aus dem λ-Kalkül (Nun, eigentlich kam es zuerst, aber das ist hier irrelevant.)

"Kombinatorische Logik kann als eine Variante des Lambda-Kalküls angesehen werden, bei der Lambda-Ausdrücke (die die funktionale Abstraktion darstellen) durch eine begrenzte Menge von Kombinatoren ersetzt werden, primitive Funktionen, bei denen gebundene Variablen fehlen." 1

Der gebräuchlichste Typ der kombinatorischen Logik ist der SK-Kombinator-Kalkül , der die folgenden Grundelemente verwendet:

K = λx.λy.x
S = λx.λy.λz.xz(yz)

Manchmal wird ein Kombinator I = λx.xhinzugefügt, der jedoch überflüssig ist, da SKK(oder tatsächlich SKxfür jeden x) er äquivalent ist I.

Alles, was Sie brauchen, ist K, S und application, um einen beliebigen Ausdruck im λ-Kalkül zu kodieren. Als Beispiel folgt eine Übersetzung von der Funktion λf.(λx.xx)(λx.f(xx))in die kombinatorische Logik:

λ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))))

Da wir die Präfixnotation verwenden, ist dies ```S`K``S``SKK``SKK``S``S`KSS`K``SKK`.

1 Quelle: Wikipedia

Die Herausforderung

Inzwischen haben Sie wahrscheinlich erraten, was ist: Schreiben Sie ein Programm, das einen gültigen λ-Ausdruck (in der oben beschriebenen Notation) als Eingabe verwendet und dieselbe Funktion ausgibt (oder zurückgibt), die in der SK-Kombinator-Rechnung umgeschrieben wurde. Beachten Sie, dass es unendlich viele Möglichkeiten gibt, dies umzuschreiben. Sie müssen nur eine der unendlichen Möglichkeiten ausgeben.

Dies ist , also gewinnt die kürzeste gültige Übermittlung (gemessen in Bytes).

Testfälle

Jeder Testfall zeigt eine mögliche Ausgabe. Der Ausdruck oben ist der äquivalente λ-Kalkülausdruck.

λ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
Ich denke dein zweiter Testfall ist nicht korrekt. Die letzte enthält eine Zahl, die nicht in Klammern steht.
Christian Sievers


Wie bist du gekommen λx.f(xx) = S(Kf)(SKK)? Sollte es nicht lieber sein λx.f(xx) = S(Kf)(SII) = S(Kf)(S(SKK)(SKK))? Beim Konvertieren λx.f(xx)bekomme ich S {λx.f} {λx.xx}was reduziert S (Kf) {λx.xx}und der Ausdruck in Klammern ist nichts anderes als das ω=λx.xx, was wir als dargestellt wissen SII = S(SKK)(SKK), oder?
BarbaraKwarc

@BarbaraKwarc Richtig, ich meinte SII, nicht SKK. Das war ein Fehler.
Esolanging Fruit

Antworten:


9

Haskell, 251 237 222 214 Bytes

15 Bytes gespart dank @ Ørjan_Johansen (siehe auch seine TIO-Links in den Bemerkungen)!

8 weitere Bytes gespart dank @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

panalysiert die Eingabe und gibt den verbleibenden nicht analysierten Teil in der ersten Komponente des resultierenden Paares zurück. Das erste Zeichen des Arguments muss ein Backtick, ein Backslash oder eine öffnende Klammer sein. Die Musterwächter pprüfen diese Fälle in dieser Reihenfolge. Im ersten Fall, der eine Anwendung bezeichnet, werden zwei weitere Ausdrücke analysiert und Emit dem Konstruktor infix zu einem Element des Datentyps kombiniert :>. Im Lambda-Fall wird der folgende Ausdruck analysiert und sofort an die aFunktion übergeben. Ansonsten ist es eine Variable, wir bekommen ihre Nummer mit der readsFunktion (die eine Liste zurückgibt) und löschen die schließende Klammer durch Mustervergleich mit (_:t).

Die aFunktion erledigt die recht bekannte Bracket-Abstraktion. Um eine Anwendung zu abstrahieren, abstrahieren wir die beiden Unterausdrücke und verwenden den SKombinator, um das Argument auf beide zu verteilen. Das ist immer richtig, aber mit mehr Code könnten wir viel besser mit Sonderfällen umgehen, um kürzere Ausdrücke zu erhalten. Die aktuelle Variable abstrahiert gibt Ioder, wenn wir nicht haben , dass SKK. Normalerweise können die übrigen Fälle nur ein a Kzur Front hinzufügen , aber wenn wir diese Notation verwenden, müssen wir die Variablen neu nummerieren, da das innere Lambda abstrahiert wird.

owandelt das Ergebnis in einen String für die Ausgabe um. fist die komplette Funktion.

Backslash ist, wie in vielen Sprachen, ein Escape-Zeichen. Daher muss es zweimal in einem String-Literal angegeben werden:

*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. In der zweiten Zeile können Sie verwenden (a,(b,v))<-p<$>p s. 2. Das '\\'kann nur sein, _wenn Sie das Match zuletzt verschieben.
Ørjan Johansen

1
Kratzen Sie den ersten Teil: Es ist kürzer, die Tupelreihenfolge zu tauschen und p(_:s)=a<$>p sfür die (verschobene) '\\'Zeile zu verwenden.
Ørjan Johansen

1
Probieren Sie es online! für Ihre aktuelle Version. Was übrigens nur 236 Bytes ist, können Sie die letzte Newline löschen.
Ørjan Johansen

2
@ Challenger5 Ich denke, es liegt hauptsächlich an der Tatsache, dass Haskell auf Lambda-Kalkulation basiert, sodass Leute, die sich mit Haskell auskennen, mit größerer Wahrscheinlichkeit von solchen Fragen angezogen werden :)
Leo

2
Sie können festlegen , pmit drei Wachen mit einem einzigen Ausdruck, die Fälle neu anordnen und eine überflüssige Paar fallen (): 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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.