Le λ-calcul , ou lambda calcul, est un système logique basé sur des fonctions anonymes. Par exemple, c'est une expression λ:
λf.(λx.xx)(λx.f(xx))
Cependant, pour les besoins de ce défi, nous simplifierons la notation:
- Passez
λ
à\
(pour faciliter la saisie):\f.(\x.xx)(\x.f(xx))
- Les
.
en-têtes in lambda ne sont pas nécessaires, nous pouvons donc les supprimer:\f(\xxx)(\xf(xx))
- Utilisez la notation de préfixe de style Unlambda avec
`
pour l'application plutôt que d'écrire les deux fonctions ensemble (pour une explication complète de la façon de procéder, voir Convertir entre les notations Lambda Calculus ):\f`\x`xx\x`f`xx
- C'est la substitution la plus compliquée. Remplacez chaque variable par un nombre entre parenthèses en fonction de la profondeur d'imbrication de la variable par rapport à l'en-tête lambda auquel elle appartient (c'est-à-dire, utilisez l' indexation De Bruijn basée sur 0 ). Par exemple, dans
\xx
(la fonction d'identité), lex
dans le corps serait remplacé par[0]
, car il appartient au premier en-tête (basé sur 0) rencontré lors du passage de l'expression de la variable à la fin;\x\y``\x`xxxy
serait converti en\x\y``\x`[0][0][1][0]
. Nous pouvons maintenant supprimer les variables dans les en-têtes, en laissant\\``\`[0][0][1][0]
.
La logique combinatoire est fondamentalement un Turing Tarpit fabriqué à partir du λ-calcul (Eh bien, en fait, il est venu en premier, mais ce n'est pas pertinent ici.)
"La logique combinatoire peut être considérée comme une variante du calcul lambda, dans lequel les expressions lambda (représentant l'abstraction fonctionnelle) sont remplacées par un ensemble limité de combinateurs, fonctions primitives dont les variables liées sont absentes." 1
Le type de logique combinatoire le plus courant est le calcul du combinateur SK , qui utilise les primitives suivantes:
K = λx.λy.x
S = λx.λy.λz.xz(yz)
Parfois, un combinateur I = λx.x
est ajouté, mais il est redondant, car SKK
(ou même SKx
pour tout x
) est équivalent à I
.
Tout ce dont vous avez besoin est K, S et l'application pour pouvoir encoder n'importe quelle expression dans le λ-calcul. À titre d'exemple, voici une traduction de la fonction λf.(λx.xx)(λx.f(xx))
en logique combinatoire:
λ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))))
Puisque nous utilisons la notation préfixe, c'est le cas ```S`K``S``SKK``SKK``S``S`KSS`K``SKK`
.
1 Source: Wikipedia
Le défi
À présent, vous avez probablement deviné ce qui est: Écrivez un programme qui prend une expression λ valide (dans la notation décrite ci-dessus) comme entrée et sorties (ou retourne) la même fonction, réécrite dans le calcul SK-combinator. Notez qu'il existe un nombre infini de façons de réécrire ceci; il vous suffit de sortir l'une des manières infinies.
Il s'agit de code-golf , donc la soumission valide la plus courte (mesurée en octets) l'emporte.
Cas de test
Chaque scénario de test montre une sortie possible. L'expression en haut est l'expression équivalente du λ-calcul.
λ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)
? N'est-ce pas plutôt le cas λx.f(xx) = S(Kf)(SII) = S(Kf)(S(SKK)(SKK))
? Lors de la conversion λx.f(xx)
, j'obtiens S {λx.f} {λx.xx}
ce qui se réduit à S (Kf) {λx.xx}
et l'expression entre crochets n'est rien d'autre que ω=λx.xx
, ce que nous savons est représenté comme SII = S(SKK)(SKK)
, non?
SII
, non SKK
. C'était une erreur.