Convertir les expressions λ en expressions SK


20

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é), le xdans 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`xxxyserait 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.xest ajouté, mais il est redondant, car SKK(ou même SKxpour 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 , 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


1
Je pense que votre deuxième cas de test n'est pas correct. Le dernier contient un nombre qui n'est pas entre parenthèses.
Christian Sievers


Comment es-tu arrivé λ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?
BarbaraKwarc

@BarbaraKwarc D'accord, je voulais dire SII, non SKK. C'était une erreur.
Esolanging Fruit

Réponses:


9

Haskell, 251 237 222 214 octets

15 octets économisés grâce à @ Ørjan_Johansen (voir aussi ses liens TIO dans les remarques)!

8 octets de plus économisés grâce à @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

panalyse l'entrée, renvoyant la partie non analysée restante dans le premier composant de la paire résultante. Le premier caractère de son argument doit être une barre oblique inverse, une barre oblique inverse ou un crochet ouvrant. Les patrons de garde de pvérifier ces cas dans cet ordre. Dans le premier cas, désignant une application, deux autres expressions sont analysées et combinées à un élément du Etype de données avec le constructeur infix :>. Dans le cas lambda, l'expression suivante est analysée et immédiatement donnée à la afonction. Sinon c'est une variable, on obtient son numéro avec la readsfonction (qui retourne une liste) et on laisse tomber la parenthèse fermante par pattern matching avec (_:t).

La afonction effectue l'abstraction des crochets bien connue. Pour résumer une application, nous résumons les deux sous-expressions et utilisons le Scombinateur pour distribuer l'argument aux deux. C'est toujours correct, mais avec plus de code, nous pourrions faire beaucoup mieux en gérant des cas spéciaux afin d'obtenir des expressions plus courtes. Abstraire la variable actuelle donne Iou, quand nous ne l' avons pas, SKK. Habituellement, les cas restants peuvent simplement ajouter un Kà l'avant, mais lorsque vous utilisez cette notation, nous devons renuméroter les variables au fur et à mesure que la lambda intérieure est abstraite.

otransforme le résultat en une chaîne de sortie. fest la fonction complète.

Comme dans de nombreuses langues, la barre oblique inverse est un caractère d'échappement, il doit donc être donné deux fois dans un littéral de chaîne:

*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. Sur la deuxième ligne, vous pouvez utiliser (a,(b,v))<-p<$>p s. 2. Le '\\'peut être juste _si vous déplacez ce match en dernier.
Ørjan Johansen

1
En fait, grattez la première partie: il est plus court d'échanger l'ordre des tuples et de l'utiliser p(_:s)=a<$>p spour la ligne (déplacée) '\\'.
Ørjan Johansen

1
Essayez-le en ligne! pour votre version actuelle. Soit dit en passant, ce n'est que de 236 octets, vous pouvez supprimer la nouvelle ligne finale.
Ørjan Johansen

2
@ Challenger5 Je pense que c'est principalement dû au fait que haskell est basé sur le calcul lambda, donc les gens compétents en haskell sont plus susceptibles d'être attirés par ce genre de questions :)
Leo

2
Vous pouvez définir pavec une seule expression avec trois gardes, réorganiser les cas et déposer une paire superflue (): 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.