Converti espressioni λ in espressioni SK


20

Il calcolo λ , o calcolo lambda, è un sistema logico basato su funzioni anonime. Ad esempio, questa è un'espressione λ:

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

Tuttavia, ai fini di questa sfida, semplificheremo la notazione:

  • Passare λa \(per semplificare la digitazione):\f.(\x.xx)(\x.f(xx))
  • Le .intestazioni in lambda non sono necessarie, quindi possiamo rilasciarle:\f(\xxx)(\xf(xx))
  • Utilizzare la notazione con prefisso in stile Unlambda con `per l'applicazione anziché scrivere insieme le due funzioni (per una spiegazione completa di come eseguire questa operazione, vedere Converti tra le notazioni di calcolo Lambda ):\f`\x`xx\x`f`xx
  • Questa è la sostituzione più complicata. Sostituisci ogni variabile con un numero tra parentesi in base alla profondità con cui la variabile è nidificata rispetto all'intestazione lambda a cui appartiene (ovvero usa l' indicizzazione De Bruijn basata su 0 ). Ad esempio, in \xx(la funzione di identità), l' xelemento nel corpo verrebbe sostituito con [0], poiché appartiene alla prima intestazione (basata su 0) riscontrata quando si attraversa l'espressione dalla variabile alla fine; \x\y``\x`xxxysarebbe convertito in \x\y``\x`[0][0][1][0]. Ora possiamo rilasciare le variabili nelle intestazioni, lasciando \\``\`[0][0][1][0].

La logica combinatoria è fondamentalmente un Tarpit di Turing fatto con il calcolo λ (Beh, in realtà, è venuto per primo, ma qui non ha importanza.)

"La logica combinatoria può essere vista come una variante del calcolo lambda, in cui le espressioni lambda (che rappresentano l'astrazione funzionale) sono sostituite da un insieme limitato di combinatori, funzioni primitive da cui sono assenti variabili associate." 1

Il tipo più comune di logica combinatoria è il calcolo del combinatore SK , che utilizza le seguenti primitive:

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

A volte I = λx.xviene aggiunto un combinatore , ma è ridondante, poiché SKK(o effettivamente SKxper qualsiasi x) è equivalente a I.

Tutto ciò di cui hai bisogno è K, S e l'applicazione per poter codificare qualsiasi espressione nel calcolo λ. Ad esempio, ecco una traduzione dalla funzione λf.(λx.xx)(λx.f(xx))alla logica combinatoria:

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

Dal momento che stiamo usando la notazione del prefisso, questo è ```S`K``S``SKK``SKK``S``S`KSS`K``SKK`.

1 Fonte: Wikipedia

La sfida

Ormai probabilmente hai indovinato cosa sia: scrivi un programma che accetta un'espressione λ valida (nella notazione sopra descritta) come input e output (o restituisce) la stessa funzione, riscritta nel calcolo del combinatore SK. Nota che ci sono un numero infinito di modi per riscriverlo; devi solo produrre uno dei modi infiniti.

Si tratta di , quindi vince l'invio valido più breve (misurato in byte).

Casi test

Ogni caso di test mostra un possibile output. L'espressione in alto è l'espressione equivalente del calcolo λ.

λ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
Penso che il tuo secondo test case non sia corretto. L'ultimo contiene un numero non tra parentesi.
Christian Sievers,


Come hai fatto λx.f(xx) = S(Kf)(SKK)? Non dovrebbe piuttosto essere λx.f(xx) = S(Kf)(SII) = S(Kf)(S(SKK)(SKK))? Durante la conversione λx.f(xx), ottengo ciò S {λx.f} {λx.xx}che si riduce a S (Kf) {λx.xx}e l'espressione tra parentesi non è altro che ω=λx.xx, che sappiamo essere rappresentato come SII = S(SKK)(SKK), giusto?
BarbaraKwarc,

@BarbaraKwarc Giusto, intendevo SII, no SKK. È stato un errore.
Esolanging Fruit,

Risposte:


9

Haskell, 251 237 222 214 byte

15 byte salvati grazie a @ Ørjan_Johansen (vedi anche i suoi link TIO nei commenti)!

Altri 8 byte salvati grazie a @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

panalizza l'input, restituendo la parte non analizzata rimanente nel primo componente della coppia risultante. Il primo carattere del suo argomento deve essere una barra retroversa, una barra rovesciata o una parentesi quadra aperta. Le guardie del motivo pcontrollano questi casi in questo ordine. Nel primo caso, che indica un'applicazione, altre due espressioni vengono analizzate e combinate in un elemento del Etipo di dati con il costruttore infix :>. Nel caso lambda, la seguente espressione viene analizzata e immediatamente assegnata alla afunzione. Altrimenti è una variabile, otteniamo il suo numero con la readsfunzione (che restituisce un elenco) e rilasciamo la parentesi di chiusura in base al modello corrispondente (_:t).

La afunzione esegue l'astrazione della parentesi abbastanza nota. Per astrarre un'applicazione, astraggiamo le due sottoespressioni e usiamo il Scombinatore per distribuire l'argomento ad entrambi. Questo è sempre corretto, ma con più codice potremmo fare molto meglio gestendo casi speciali al fine di ottenere espressioni più brevi. Astraendo la variabile corrente dà Io, quando non abbiamo che, SKK. Di solito i casi rimanenti possono semplicemente aggiungere a Kin primo piano, ma quando si usa questa notazione dobbiamo rinumerare le variabili mentre la lambda interna viene sottratta.

otrasforma il risultato in una stringa per l'output. fè la funzione completa.

Come in molte lingue, la barra rovesciata è un carattere di escape, quindi deve essere data due volte in una stringa letterale:

*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. Sulla seconda riga, è possibile utilizzare (a,(b,v))<-p<$>p s. 2. '\\'Può essere solo _se si sposta quella corrispondenza per ultima.
Ørjan Johansen

1
In realtà, gratta la prima parte: è più breve scambiare l'ordine delle tuple e usarlo p(_:s)=a<$>p sper la '\\'linea (spostata) .
Ørjan Johansen

1
Provalo online! per la tua versione attuale. Che tra l'altro è solo 236 byte, è possibile eliminare la nuova riga finale.
Ørjan Johansen

2
@ Challenger5 Penso che sia principalmente dovuto al fatto che haskell si basa sul calcolo lambda, quindi le persone competenti in haskell hanno maggiori probabilità di essere attratte da questo tipo di domande :)
Leo

2
È possibile definire pcon una sola espressione con tre guardie, riorganizzare i casi e rilasciare un paio di superfluo (): 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.