¿Cómo funciona el rompecabezas yin-yang?
Pregunta
Estoy tratando de comprender la semántica de call/cc en Scheme, y la página de Wikipedia sobre continuaciones muestra el rompecabezas yin-yang como ejemplo:
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))) )
(yin yang))
Debería generar @*@**@***@****@...
,
pero no entiendo por qué;Esperaría que saliera @*@*********
...
¿Alguien puede explicar en detalle por qué el rompecabezas yin-yang funciona de la manera en que funciona?
Solución
No creo que entiendo éste totalmente, pero sólo puedo pensar en uno ( muy ondulada a mano) explicación para esto:
- El primer @ y * se imprimen al
yin
yyang
están obligados por primera vez en lalet*
. Se aplica(yin yang)
, y se remonta a la parte superior, justo después de la primera llamada / cc está terminado. - El siguiente @ y * se imprimen, y luego otro * se imprime porque esta vez a través,
yin
es re-unido al valor de la segunda llamada / cc. -
(yin yang)
se aplica de nuevo, pero esta vez está ejecutando en el entorno delyang
originales , dondeyin
se une a la primera llamada / cc, por lo que el control vuelve a imprimir otro @. El argumentoyang
contiene la continuación que fue re-capturado en la segunda pasada por, que como ya hemos visto, dará lugar a la impresión**
. Así que en este tercer paso,@*
será impreso, a continuación, esta continuación de doble estrella se invoca la impresión, por lo que termina con 3 estrellas, y luego continuar esta triple estrellas se re-capturado, ...
Otros consejos
Comprensión Esquema
Creo que al menos la mitad del problema con la comprensión de este rompecabezas es la sintaxis de esquema, que la mayoría no están familiarizados.
En primer lugar, yo personalmente encontrar el call/cc x
a ser más difícil de comprender que la alternativa equivalente, x get/cc
. Todavía llamadas x, pasándole la continuación actual , pero de alguna manera es más susceptible de ser representado en mi circuito cerebral.
Con esto en mente, la construcción se convierte simplemente en (call-with-current-continuation (lambda (c) c))
get-cc
. Ahora estamos en esto:
(let* ((yin
((lambda (cc) (display #\@) cc) get-cc))
(yang
((lambda (cc) (display #\*) cc) get-cc)) )
(yin yang))
El siguiente paso es el cuerpo de la lambda interior. (display #\@) cc
, en la sintaxis más familiar (al menos para mí) significa print @; return cc;
. Mientras estamos en ello, también vamos a reescribir lambda (cc) body
como function (arg) { body }
, remover un montón de paréntesis, y las llamadas a funciones cambio en el C-como sintaxis, para conseguir esto:
(let* yin =
(function(arg) { print @; return arg; })(get-cc)
yang =
(function(arg) { print *; return arg; })(get-cc)
yin(yang))
Está empezando a tener más sentido ahora. Ahora es un pequeño paso para volver a escribir esta completamente en C-como sintaxis (o JavaScript similar, si se prefiere), para conseguir esto:
var yin, yang;
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);
La parte más difícil es ahora más, hemos decodificado esto desde Esquema! Es una broma; solo era difícil porque no tenía ninguna experiencia previa con el Esquema. Por lo tanto, vamos a llegar a averiguar cómo funciona realmente.
Una introducción a continuaciones
Observe el núcleo extrañamente formulado de Yin y Yang: se define una función e inmediatamente llama . Se ve como (function(a,b) { return a+b; })(2, 3)
, que se puede simplificar a 5
. Sin embargo, la simplificación de las llamadas dentro del yin / yang, sería un error, porque no estamos pasándole un valor ordinario. Estamos pasando la función de un continuación .
A continuación es una bestia extraña a primera vista. Considere el programa mucho más sencillo:
var x = get-cc;
print x;
x(5);
Inicialmente x
se establece en el objeto continuidad actual (oso conmigo), y print x
es ejecutado, la impresión de algo así como <ContinuationObject>
. Hasta aquí todo bien.
Sin embargo, una continuación es como una función; se le puede llamar con un argumento. Lo que hace es:. Tomar el argumento, y luego salto a donde quiera que se creó la continuación, la restauración de todo el contexto, y haciéndolo de manera que vuelve get-cc
este argumento
En nuestro ejemplo, el argumento es 5
, así que básicamente saltar de nuevo a la derecha en el medio de esa declaración var x = get-cc
, sólo que esta vez vuelve get-cc
5
. Así se convierte en x
5
, y la siguiente declaración continúa a imprimir 5. Después de que tratamos de 5(5)
llamada, que es un tipo de error, y los errores en el programa.
Observe que llamar a la continuación es un salto , no una llamada. Nunca vuelve de nuevo a donde fue llamado la continuación. Eso es importante.
¿Cómo funciona el programa
Si ha seguido eso, entonces no te hagas ilusiones: esta parte es realmente el más difícil. Aquí está nuestro programa de nuevo, dejando caer las declaraciones de variables porque se trata de pseudo-código de todas formas:
yin = (function(arg) { print @; return arg; })(get-cc);
yang = (function(arg) { print *; return arg; })(get-cc);
yin(yang);
La primera línea de tiempo 1 y 2 son golpeados, son simples ahora: conseguir la continuación, llamar a la función (arg), @
de impresión, a cambio, tienda que la continuación en yin
. Lo mismo con yang
. Nosotros ahora hemos impreso @*
.
A continuación, se llama a la continuación en yin
, pasándole yang
. Esto nos hace saltar a la línea 1, interior derecho que get-cc, y hacerlo volver yang
lugar. El valor de yang
ahora se pasa a la función, que imprime @
, y luego devuelve el valor de yang
. Ahora yin
está asignado que la continuación que tiene yang
. A continuación simplemente procede a la línea 2: Primeros c / c, *
imprimir, almacenar ºe c / c en yang
. Ahora tenemos @*@*
. Y, por último, vamos a la línea 3.
Recuerde que yin
tiene ahora la continuación de cuando la línea 2 se ejecuta en primer lugar. Así que saltamos a la línea 2, la impresión de una segunda *
y actualizar yang
. Ahora tenemos @*@**
. Por último, llama la continuación yin
nuevo, que saltará a la línea 1, la impresión de un @
. Y así. Francamente, en este momento mi cerebro se produce una excepción OutOfMemory y perder la noción de todo. Pero, al menos, llegamos a @*@**
!
Esto es difícil de seguir y aún más difícil de explicar, obviamente. La manera perfecta de hacer esto sería dar un paso a través de él en un depurador que puede representar continuaciones, pero por desgracia, no sé de ninguna. Espero que hayan disfrutado de este; Ciertamente tengo.
Reflexiones primero, posible respuesta al final.
Creo que el código puede ser re-escrito así:
; call (yin yang)
(define (yy yin yang) (yin yang))
; run (call-yy) to set it off
(define (call-yy)
(yy
( (lambda (cc) (display #\@) cc) (call/cc (lambda (c) c)) )
( (lambda (cc) (display #\*) cc) (call/cc (lambda (c) c)) )
)
)
O con algunas declaraciones de visualización adicionales para ayudar a ver lo que está sucediendo:
; create current continuation and tell us when you do
(define (ccc)
(display "call/cc=")
(call-with-current-continuation (lambda (c) (display c) (newline) c))
)
; call (yin yang)
(define (yy yin yang) (yin yang))
; run (call-yy) to set it off
(define (call-yy)
(yy
( (lambda (cc) (display "yin : ") (display #\@) (display cc) (newline) cc)
(ccc) )
( (lambda (cc) (display "yang : ") (display #\*) (display cc) (newline) cc)
(ccc) )
)
)
O como esto:
(define (ccc2) (call/cc (lambda (c) c)) )
(define (call-yy2)
(
( (lambda (cc) (display #\@) cc) (ccc2) )
( (lambda (cc) (display #\*) cc) (ccc2) )
)
)
Posible respuesta ??b>
Esto puede no ser correcta, pero voy a tener una oportunidad.
Creo que el punto clave es que una 'llamada' continuación devuelve la pila a un estado anterior - como si nada hubiera sucedido. Por supuesto que no sabe que el monitoreo por presentar caracteres @
y *
.
En principio definimos yin
ser un A
continuidad que va a hacer esto:
1. restore the stack to some previous point
2. display @
3. assign a continuation to yin
4. compute a continuation X, display * and assign X to yang
5. evaluate yin with the continuation value of yang - (yin yang)
Pero si llamamos una continuación yang
, esto sucede:
1. restore the stack to some point where yin was defined
2. display *
3. assign a continuation to yang
4. evaluate yin with the continuation value of yang - (yin yang)
Comenzamos aquí.
La primera vez que se obtiene a través yin=A
y yang=B
como yin
y yang
están siendo inicializado.
The output is @*
(Ambas continuaciones A
y B
se calculan.)
Ahora (yin yang)
se evalúa como (A B)
por primera vez.
sabe lo que hace A
. Hace esto:
1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B, we don't compute it.
4. compute another continuation B', display * and assign B' to yang
The output is now @*@*
5. evaluate yin (B) with the continuation value of yang (B')
Ahora (yin yang)
se evalúa como (B B')
.
sabe lo que hace B
. Hace esto:
1. restore the stack - back to the point where yin was already initialised.
2. display *
3. assign a continuation to yang - this time, it is B'
The output is now @*@**
4. evaluate yin with the continuation value of yang (B')
Dado que la pila se restauró hasta el punto en yin=A
, (yin yang)
se evalúa como (A B')
.
sabe lo que hace A
. Hace esto:
1. restores the stack - back to the point where yin and yang were being initialised.
2. display @
3. assign a continuation to yin - this time, it is B', we don't compute it.
4. compute another continuation B", display * and assign B" to yang
The output is now @*@**@*
5. evaluate yin (B') with the continuation value of yang (B")
sabe lo que hace B'
. Hace esto:
1. restore the stack - back to the point where yin=B.
2. display *
3. assign a continuation to yang - this time, it is B"
The output is now @*@**@**
4. evaluate yin (B) with the continuation value of yang (B")
Ahora (yin yang)
se evalúa como (B B")
.
sabe lo que hace B
. Hace esto:
1. restore the stack - back to the point where yin=A and yang were being initialised.
2. display *
3. assign a continuation to yang - this time, it is B'"
The output is now @*@**@***
4. evaluate yin with the continuation value of yang (B'")
Dado que la pila se restauró hasta el punto en yin=A
, (yin yang)
se evalúa como (A B'")
.
.......
Creo que tenemos un patrón ahora.
Cada vez que llamamos (yin yang)
nos bucle a través de una pila de continuaciones B
hasta que volvamos a cuando yin=A
y presentamos @
. El ciclo que recorre la pila de continuaciones B
escribir un *
cada vez.
(I sería muy feliz si esto es más o menos bien!)
Gracias por la pregunta.
El rompecabezas de YinYang está escrito en Scheme.Supongo que conoces la sintaxis básica de Scheme.
Pero supongo que no lo sabes let*
o call-with-current-continuation
, Explicaré estas dos palabras clave.
Explicar let*
Si ya lo sabe, puede pasar a Explain call-with-current-continuation
let*
, que parece let
, actúa como let
, pero evaluará sus variables definidas (el (yin ...)
y (yang ...)
) uno por uno y con entusiasmo.Eso significa que primero evaluará yin
, y que yang
.
Puedes leer más aquí:Usando el esquema Let in
Explicar call-with-current-continuation
Si ya lo sabe, puede pasar a Yin-Yang puzzle
.
Es un poco difícil de explicar call-with-current-continuation
.Así que usaré una metáfora para explicarlo.
Imagen un mago que conocía un hechizo, que era call-with-current-continuation
.Una vez que lanzaba el hechizo, creaba un nuevo universo y se enviaba a sí mismo a él.Pero él podía no hacer nada en el nuevo universo, pero esperando a que alguien lo llame por su nombre.Una vez sido llamado, el mago regresaría al universo original, teniendo al pobre hombre, 'alguien', en la mano, y continuaría con su vida de mago.Si no se le llamaba, cuando terminaba el nuevo universo, el mago también regresaba al universo original.
Ok, seamos más técnicos.
call-with-current-continuation
es una función que acepta una función como parámetro.Una vez que llame call-with-current-continuation
con una función F
, empaquetará el entorno de ejecución actual, que se llama current-continuation
, como parámetro C
, y envíelo a la función F
, y ejecutar F
.Entonces todo el programa se convierte en (F C)
.O ser más JavaScript: F(C);
. C
actúa como una función.Si C
no es llamado F
, entonces es un programa ordinario, cuando F
devoluciones, call-with-current-continuation
tiene un valor como F
el valor de retorno.Pero si C
se llama con un parámetro V
, cambiará todo el programa nuevamente.El programa vuelve a cambiar a estado cuando call-with-current-continuation
sido llamado.Pero ahora call-with-current-continuation
produce un valor, que es V
.Y el programa continúa.
Tomemos un ejemplo.
(define (f return)
(return 2)
3)
(display (f whatever)) ;; 3
(display (call-with-current-continuation f)) ;; 2
(display (call-with-current-continuation (lambda (x) 4))) ;; 4
El primero display
salida 3
, de causa.
Pero el segundo display
salida 2
.¿Por qué?
Vamos a sumergirnos en ello.
Al evaluar (display (call-with-current-continuation f))
, primero evaluará (call-with-current-continuation f)
.Sabemos que cambiará todo el programa para
(f C)
Considerando la definición de f
, tiene un (return 2)
.Debemos evaluar (C 2)
.Fue entonces cuando el continuation
ser llamado.Entonces cambia todo el programa de nuevo a
(display (call-with-current-continuation f))
Pero ahora, call-with-current-continuation
tiene valor 2
.Entonces el programa se convierte en:
(display 2)
Rompecabezas del Yin-Yang
Veamos el rompecabezas.
(let* ((yin
((lambda (cc) (display #\@) cc) (call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc) (call-with-current-continuation (lambda (c) c)))))
(yin yang))
Hagámoslo más legible.
(define (id c) c)
(define (f cc) (display #\@) cc)
(define (g cc) (display #\*) cc)
(let* ((yin
(f (call-with-current-continuation id)))
(yang
(g (call-with-current-continuation id))))
(yin yang))
Ejecutemos el programa en nuestro cerebro.
Ronda 0
let*
haznos evaluar yin
primero. yin
es
(f (call-with-current-continuation id))
Entonces evaluamos (call-with-current-continuation id)
primero.Empaqueta el entorno actual, al que llamamos C_0
para distinguirlo con otra continuación en la línea de tiempo, y entra en un universo completamente nuevo: id
.Pero id
solo devoluciones C_0
.
Deberíamos Recordar qué C_0
lo es. C_0
es un programa como este:
(let* ((yin
(f ###))
(yang
(g (call-with-current-continuation id))))
(yin yang))
###
es un marcador de posición, que en el futuro se llenará con el valor que C_0
retoma.
Pero id
solo devoluciones C_0
.No llama C_0
.Si llama, entraremos C_0
el universo .Pero no fue así, así que continuamos evaluando yin
.
(f C_0) ;; yields C_0
f
es una función como id
, pero tiene un efecto secundario outp @
.
Entonces, la salida del programa @
y dejar yin
ser C_0
.Ahora el programa se convierte en
(let* ((yin C_0)
(yang
(g (call-with-current-continuation id))))
(yin yang))
Después yin
evaluado, comenzamos a evaluar yang
. yang
es
(g (call-with-current-continuation id))
call-with-current-continuation
aquí crea otra continuación, llamada C_1
. C_1
es:
(let* ((yin C_0)
(yang
(g ###)))
(yin yang))
###
es marcador de posición.Tenga en cuenta que en esta continuación, yin
se determina el valor (eso es lo que let*
hacer).Estamos seguros de que yin
el valor es C_0
aquí.
Desde (id C_1)
es C_1
, entonces yang
el valor es
(g C_1)
g
tiene un efecto secundario outp *
.Así lo hace el programa.
yang
el valor es ahora C_1
.
Por ahora, hemos mostrado @*
Así que ahora se convierte en:
(let* ((yin C_0)
(yang C_1))
(yin yang))
Como ambos yin
y yang
resueltos, debemos evaluar (yin yang)
.Es
(C_0 C_1)
¡Santa MIERDA!
Pero finalmente, C_0
se llama.Así que volamos a la C_0
universo y olvídate de estas mierdas.Nunca volveremos a este universo de nuevo.
Ronda 1
C_0
tomar con C_1
atrás.El programa ahora se convierte en (Si olvidas qué C_0
significa, vuelve a verlo):
(let* ((yin
(f C_1))
(yang
(g (call-with-current-continuation id))))
(yin yang))
Ah, encontramos que yin
el valor aún no está determinado.Entonces lo evaluamos.En proceso de evaluación yin
, emitimos un @
como f
el efecto secundario .Y sabemos yin
es C_1
ahora.
Comenzamos a evaluar yang
, nos encontramos con call-with-current-continuation
otra vez.Somos practicantes.Creamos una continuación C_2
que significa:
(let* ((yin C_1)
(yang
(g ###)))
(yin yang))
Y mostramos un *
como g
ejecutando.Y venimos aquí
(let* ((yin C_1)
(yang C_2))
(yin yang))
Así que tenemos:
(C_1 C_2)
Sabes a dónde vamos.Vamos a C_1
el universo .Lo recordamos de memoria(o copiamos y pegamos desde la página web).Es ahora:
(let* ((yin C_0)
(yang
(g C_2)))
(yin yang))
Sabemos en C_1
el universo de, yin
el valor ha sido determinado.Entonces comenzamos a evaluar yang
.A medida que nos practiquen, te diré directamente que se muestra *
y se convierte en:
(C_0 C_2)
Ahora hemos impreso @*@**
, y vamos a C_0
's universo tomando con C_2
.
Ronda 2
A medida que nos practiquen, te diré que mostramos'@', yin
es C_2
, y creamos una nueva continuación C_3
, que significa:
(let* ((yin C_2)
(yang
(g ###)))
(yin yang))
Y mostramos *
, yang
es C_3
, y se convierte en
(C_2 C_3)
Y podemos continuar.Pero me detendré aquí, te he mostrado cuáles son las primeras salidas de Yin-Yang puzzle.
Por qué el número de *
¿aumentos?
Ahora tu cabeza está llena de detalles.Te haré un resumen.
Usaré una sintaxis similar a Haskell para simplificar.Y cc
es la abreviatura de call-with-current-continuation
.
Cuando #C_i#
está entre corchetes por #
, significa que la continuación se crea aquí. ;
salida de medios
yin = f cc id
yang = g cc id
yin yang
---
yin = f #C_0# ; @
yang = g cc id
yin yang
---
yin = C_0
yang = g #C_1# ; *
yin yang
---
C_0 C_1
---
yin = f C_1 ; @
yang = g #C_2# ; *
yin yang
---
C_1 C_2
---
yin = C_0
yang = g C_2 ; *
yin yang
---
C_0 C_2
---
yin = f C_2 ; @
yang = g #C_3#; *
yin yang
---
C_2 C_3
---
yin = C_1
yang = g C_3 ; *
yin yang
---
C_1 C_3
---
yin = C_0
yang = g C_3 ; *
yin yang
---
C_0 C_3
Si observa cuidadosamente, será obvio para usted que
- Hay muchos universos (de hecho infinitos), pero
C_0
es el único universo que comenzó porf
.Otros es iniciado porg
. C_0 C_n
siempre hace una nueva continuaciónC_max
.Es porqueC_0
es el primer universo queg cc id
tiene no sido ejecutado.C_0 C_n
también mostrar@
.C_n C_m
que n no es 0 se mostrará*
.- De vez en cuando se deduce el programa a
C_0 C_n
, y demostraré queC_0 C_n
está separado por más y más otras expresiones, lo que conduce a@*@**@***...
Un poco de matemáticas
Asumir (¡n != 0) es el número más grande en todas las continuaciones, y luego C_0 C_n
se llama.
Suposición:Cuando C_0 C_n
se llama, C_n
es la continuación numerada máxima actual.
Ahora es creado por C_0 C_n
como esto:
yin = f C_n ; @
yang = g #C_{n+1}#
yin yang
Entonces concluimos que:
Teorema I.Si C_0 C_n
se llama, Producirá una continuación , en el cual yin
es C_n
.
Entonces el siguiente paso es C_n C_{n+1}
.
yin = C_{n-1}
yang = g C_{n+1} ; *
yin yang
La razón por la que yin
es C_{n-1}
¿es Eso Cuando C_n
siendo creado obedeció Teorema I.
Y entonces C_{n-1} C_{n+1}
se llama, y sabemos cuándo C_{n-1}
es creado, también obedeció Teorema I.Entonces tenemos C_{n-2} C_{n+1}
.
C_{n+1}
es la variación interna.Entonces tenemos el segundo teorema:
Teorema II.Si C_n C_m
cual n < m
y n > 0
se llama, Se convertirá en C_{n-1} C_m
.
Y hemos comprobado manualmente C_0
C_1
C_2
C_3
.Obedecen a la Suposición y a todos los Teoremas.Y sabemos cómo primero @
y *
se crea.
Entonces podemos escribir patrones a continuación.
C_0 C_1 ; @ *
C_[1-0] C_2 ; @ * *
C_[2-0] C_3 ; @ * * *
...
No es tan estricto, pero me gustaría escribir:
Q. E. D.
Como dijo otra respuesta, que (call-with-current-continuation (lambda (c) c))
primera simplificar con get-cc
.
(let* ((yin
((lambda (cc) (display #\@) cc) get-cc))
(yang
((lambda (cc) (display #\*) cc) get-cc)) )
(yin yang))
Ahora, los dos lambda son sólo una función idéntica asociado con efectos secundarios. Vamos a llamar a las funciones f
(por display #\@
) y g
(por display #\*
).
(let* ((yin (f get-cc))
(yang (g get-cc)))
(yin yang))
A continuación necesidad de elaborar el orden de evaluación. Para que quede claro, voy a presentar una "expresión de paso" que hace que cada etapa de evaluación explícita. En primer lugar vamos a preguntar: ¿cuál es la función anterior requiere
Requiere definiciones de f
y g
. En la expresión de paso, se escribe
s0 f g =>
El primer paso es calcular yin
, pero que requieren de evaluar (f get-cc)
, y la posterior requiere get-cc
.
En términos generales, get-cc
le da un valor que representa la "continuación actual". Digamos que este es s1
ya que este es el siguiente paso. Así que escribimos
s0 f g => s1 f g ?
s1 f g cc =>
Tenga en cuenta que los parámetros son Scopeless, lo que significa la f
y g
en s0
y s1
no son necesarias la misma y que son sólo para ser utilizado dentro del paso actual. Esto hace que la información de contexto explícito. Ahora, ¿cuál es el valor de cc
? Ya que es "la continuación actual", que es una especie de la misma s1
con f
y g
unido al mismo valor.
s0 f g => s1 f g (s1 f g)
s1 f g cc =>
Una vez que tenemos cc
, podemos evaluar f get-cc
. Además, dado que f
no se utiliza en el código siguiente, que no tenemos que pasar en este valor.
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin =>
El siguiente es el similar para yang
. Pero ahora tenemos un valor de más de transmitir:. yin
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin => s3 g yin (s3 g yin)
s3 g yin cc => s4 yin (g cc)
s4 yin yang =>
Finalmente, el último paso es aplicar yang
a yin
.
s0 f g => s1 f g (s1 f g)
s1 f g cc => s2 g (f cc)
s2 g yin => s3 g yin (s3 g yin)
s3 g yin cc => s4 yin (g cc)
s4 yin yang => yin yang
Esto terminó el constructo de expresión paso. Traducir de nuevo al esquema es simple:
(let* ([s4 (lambda (yin yang) (yin yang))]
[s3 (lambda (yin cc) (s4 yin (g cc))]
[s2 (lambda (yin) (s3 yin ((lambda (cc) (s3 yin cc))))]
[s1 (lambda (cc) (s2 (f cc)))])
(s1 s1))
El orden de evaluación detallada (aquí el lambda dentro del cuerpo de s2
se expresó simplemente como s3 yin
evaluación parcial en lugar de (lambda (cc) (s3 yin cc))
):
(s1 s1)
=> (s2 (f s1))
=> @|(s2 s1)
=> @|(s3 s1 (s3 s1))
=> @|(s4 s1 (g (s3 s1)))
=> @*|(s4 s1 (s3 s1))
=> @*|(s1 (s3 s1))
=> @*|(s2 (f (s3 s1)))
=> @*@|(s2 (s3 s1))
=> @*@|(s2 (s3 s1))
=> @*@|(s3 (s3 s1) (s3 (s3 s1)))
=> @*@|(s4 (s3 s1) (g (s3 (s3 s1))))
=> @*@*|(s4 (s3 s1) (s3 (s3 s1)))
=> @*@*|(s3 s1 (s3 (s3 s1)))
=> @*@*|(s4 s1 (g (s3 (s3 s1))))
=> @*@**|(s4 s1 (s3 (s3 s1)))
=> @*@**|(s1 (s3 (s3 s1)))
=> ...
(Recuerde que, al evaluar s2
o s4
, el parámetro se va a evaluar primero
Esta un viejo rompecabezas desde el maestro de la ofuscación David Madore, quien creado unlambda. El rompecabezas se ha discutido varias comp.lang.scheme veces.
Una buena solución de Taylor Campbell: https://groups.google.com/d/msg/comp .lang.scheme / pUedvrKYY5w / uIjTc_T1LOEJ
El post original de David Madore (1999): https://groups.google.com/d/msg/comp .lang.scheme / Fysq_Wplxsw / awxEZ_uxW20J