2008-09-02 10 views

Respuesta

41

Imagina si cada línea en tu programa fuera una función separada. Cada uno acepta, como parámetro, la siguiente línea/función para ejecutar.

Usando este modelo, puede "pausar" la ejecución en cualquier línea y continuarla más tarde. También puede hacer cosas ingeniosas, como saltar temporalmente la pila de ejecución para recuperar un valor, o guardar el estado de ejecución actual en una base de datos para recuperarlo más tarde.

+0

guau, eso en realidad tiene sentido. ¡Gracias! – minimalpop

+0

Sí, muy buena explicación! +1 de mí;) – Alfred

+4

Me encanta, pero ¿qué parte es exactamente la continuación? – user2023370

9

Un aviso, este ejemplo no es conciso ni excepcionalmente claro. Esta es una demostración de una poderosa aplicación de continuaciones. Como programador VB/ASP/C#, es posible que no esté familiarizado con el concepto de acumulación de sistemas o estado de ahorro, por lo que el objetivo de esta respuesta es una demostración y no una explicación.

Las continuas son extremadamente versátiles y son una forma de guardar el estado de ejecución y reanudarlo más tarde. He aquí un pequeño ejemplo de un entorno multihilo cooperativa usando continuaciones en el Esquema:

(Suponga que las operaciones de puesta en cola y el trabajo quitar de la cola como se espera en una cola global no se define aquí)

(define (fork) 
    (display "forking\n") 
    (call-with-current-continuation 
    (lambda (cc) 
    (enqueue (lambda() 
       (cC#f))) 
    (cC#t)))) 

(define (context-switch) 
    (display "context switching\n") 
    (call-with-current-continuation 
    (lambda (cc) 
    (enqueue 
     (lambda() 
     (cc 'nothing))) 
    ((dequeue))))) 

(define (end-process) 
    (display "ending process\n") 
    (let ((proc (dequeue))) 
    (if (eq? proc 'queue-empty) 
     (display "all processes terminated\n") 
     (proc)))) 

Esto proporciona tres verbos que una función puede usar: fork, context-switch y end-process. La operación de horquilla bifurca el hilo y devuelve #t en una instancia y #f en otra. La operación de cambio de contexto cambia entre subprocesos y el proceso final finaliza un subproceso.

He aquí un ejemplo de su uso:

(define (test-cs) 
    (display "entering test\n") 
    (cond 
    ((fork) (cond 
       ((fork) (display "process 1\n") 
         (context-switch) 
         (display "process 1 again\n")) 
       (else (display "process 2\n") 
        (end-process) 
        (display "you shouldn't see this (2)")))) 
    (else (cond ((fork) (display "process 3\n") 
         (display "process 3 again\n") 
         (context-switch)) 
       (else (display "process 4\n"))))) 
    (context-switch) 
    (display "ending process\n") 
    (end-process) 
    (display "process ended (should only see this once)\n")) 

La salida debe ser ejemplos

entering test 
forking 
forking 
process 1 
context switching 
forking 
process 3 
process 3 again 
context switching 
process 2 
ending process 
process 1 again 
context switching 
process 4 
context switching 
context switching 
ending process 
ending process 
ending process 
ending process 
ending process 
ending process 
all processes terminated 
process ended (should only see this once) 

Aquellos que han estudiado bifurcan y con hilo en una clase se dan a menudo similares a este. El propósito de esta publicación es demostrar que con las continuaciones puede lograr resultados similares dentro de un solo hilo al guardar y restaurar su estado - su continuación - manualmente.

P.S. - Creo que recuerdo algo similar a esto en On Lisp, así que si quieres ver el código profesional, debes consultar el libro.

4

Básicamente, una continuación es la capacidad de una función para detener la ejecución y luego retomarla donde la dejó en un momento posterior. En C#, puede hacer esto usando la palabra clave yield. Puedo entrar en más detalles si lo desea, pero quería una explicación concisa. ;-)

2

Todavía me estoy "acostumbrando" a las continuaciones, pero una forma de pensar en ellas que me parece útil es como abstracciones del concepto de contador de programas (PC). Una PC "apunta" a la siguiente instrucción para ejecutar en la memoria, pero por supuesto que la instrucción (y casi todas las instrucciones) apunta, implícita o explícitamente, a la instrucción que sigue, así como a las instrucciones que el servicio debe interrumpir. (Incluso una instrucción NOOP realiza un JUMP implícitamente a la siguiente instrucción en la memoria. Pero si ocurre una interrupción, generalmente implicará un JUMP a alguna otra instrucción en la memoria.)

Cada punto potencialmente "activo" en un programa En memoria, el control que podría saltar en cualquier punto dado es, en cierto sentido, una continuación activa.Otros puntos que se pueden alcanzar son las continuaciones potencialmente activas, pero, más concretamente, son continuaciones que son potencialmente "calculadas" (dinámicamente, quizás) como resultado de alcanzar una o más de las continuaciones actualmente activas.

Esto parece un poco fuera de lugar en las introducciones tradicionales a continuaciones, en las que todos los hilos de ejecución pendientes se representan explícitamente como continuaciones en código estático; pero toma en cuenta el hecho de que, en computadoras de propósito general, la PC apunta a una secuencia de instrucciones que potencialmente podría cambiar el contenido de la memoria que representa una parte de esa secuencia de instrucciones, creando así una nueva (o modificada, si se quiere)) Continuación sobre la marcha, una que no existe realmente a partir de las activaciones de continuaciones que preceden a esa creación/modificación.

Así continuación se puede ver como un modelo de alto nivel de la PC, por lo que conceptualmente subsume llamada a procedimiento ordinario/retorno (tal como lo hizo el hierro antiguo procedimiento de llamada/retorno a través de SALTO de bajo nivel, también conocido como IR, instrucciones más grabación de la PC en llamada y restauración de la misma a la vuelta) así como excepciones, hilos, corutinas, etc.

Así como la PC señala que los cálculos tendrán lugar en "el futuro", una continuación hace lo mismo cosa, pero a un nivel más alto, más abstracto. La PC se refiere implícitamente a la memoria más todas las ubicaciones de memoria y registros "vinculados" a cualquier valor, mientras que una continuación representa el futuro a través de las abstracciones apropiadas para el lenguaje.

Por supuesto, si bien normalmente puede haber una sola PC por computadora (procesador central), de hecho hay muchas entidades PC-ish "activas", como se mencionó anteriormente. El vector de interrupción contiene un montón, la pila un montón más, ciertos registros pueden contener algunos, etc. Se "activan" cuando sus valores se cargan en la PC de hardware, pero las continuaciones son abstracciones del concepto, no PC o su equivalente preciso (no hay un concepto inherente de una continuación "maestra", aunque a menudo pensamos y codificamos en esos términos para mantener las cosas bastante simples).

En esencia, una continuación es una representación de "qué hacer a continuación cuando se invoca", y, como tal, puede ser (y, en algunos idiomas y en los programas de estilo de paso de continuación, a menudo es) un primer- objeto de clase que se crea una instancia, se pasa y se descarta como cualquier otro tipo de datos, y muy parecido a cómo una computadora clásica trata las ubicaciones de memoria con respecto a la PC, casi intercambiable con enteros comunes.

11

Probablemente las entiendas mejor de lo que creías.

Las excepciones son un ejemplo de continuación de "solo hacia arriba". Permiten que el código en el fondo de la pila llame a un manejador de excepciones para indicar un problema.

ejemplo Python:

try: 
    broken_function() 
except SomeException: 
    # jump to here 
    pass 

def broken_function(): 
    raise SomeException() # go back up the stack 
    # stuff that won't be evaluated 

generadores son ejemplos de continuaciones "hacia abajo sólo". Permiten que el código vuelva a entrar en un bucle, por ejemplo, para crear nuevos valores.

ejemplo Python:

def sequence_generator(i=1): 
    while True: 
     yield i # "return" this value, and come back here for the next 
     i = i + 1 

g = sequence_generator() 
while True: 
    print g.next() 

En ambos casos, estos se tuvo que añadir a la lengua específica mientras que en un lenguaje con continuaciones, el programador puede crear estas cosas donde no están disponibles.

7

Una forma de pensar en una continuación es como una pila de procesadores.Cuando "call-with-current-continuation c" llama a su función "c", y el parámetro pasado a "c" es su pila actual con todas sus variables automáticas (representada como otra función más, llámelo "k "). Mientras tanto, el procesador comienza a crear una nueva pila. Cuando llama a "k", ejecuta una instrucción "return from subroutine" (RTS) en la pila original, saltando de vuelta al contexto de la "call-with-current-continuation" original ("call-cc" a partir de ahora encendido) y permitiendo que su programa continúe como antes. Si pasó un parámetro a "k", se convierte en el valor de retorno de "call-cc".

Desde el punto de vista de la pila original, el "call-cc" se parece a una llamada de función normal. Desde el punto de vista de "c", tu pila original parece una función que nunca regresa.

Hay una vieja broma sobre un matemático que capturó un león en una jaula subiéndose a la jaula, cerrándola y declarándose estar fuera de la jaula mientras todo lo demás (incluido el león) estaba dentro de ella. Las continuas son un poco como la jaula, y "c" es un poco como el matemático. Su programa principal cree que "c" está dentro de él, mientras que "c" cree que su programa principal está dentro de "k".

Puede crear estructuras de flujo de control arbitrarias usando continuaciones. Por ejemplo, puede crear una biblioteca de subprocesos. "rendimiento" utiliza "call-cc" para poner la continuación actual en una cola y luego salta a la del encabezado de la cola. Un semáforo también tiene su propia cola de continuaciones suspendidas, y un hilo se reprograma sacandolo de la cola del semáforo y poniéndolo en la cola principal.

2

En C#, tiene acceso a dos continuaciones. Uno, al que se accede a través del return, permite que un método continúe desde donde fue llamado. El otro, al que se accede a través de throw, permite que un método continúe en la coincidencia más cercana catch.

Algunos idiomas le permiten tratar estos enunciados como valores de primera clase, por lo que puede asignarlos y pasarlos en variables. Lo que esto significa es que puede esconder el valor de return o de throw y llamarlos más tarde cuando esté realmente listo para regresar o tirar.

Continuation callback = return; 
callMeLater(callback); 

Esto puede ser útil en muchas situaciones. Un ejemplo es como el anterior, donde desea detener el trabajo que está realizando y reanudarlo más tarde cuando sucede algo (como obtener una solicitud web, o algo así).

Los estoy usando en un par de proyectos en los que estoy trabajando. En una, los estoy usando para poder suspender el programa mientras espero IO en la red, y luego reanudo el programa más tarde. En el otro, estoy escribiendo un lenguaje de programación en el que le doy acceso al usuario a continuaciones como valores para que puedan escribir return y throw para ellos mismos o cualquier otro flujo de control, como while loops, sin necesidad de hacerlo para ellos.

1

Piensa en los hilos. Se puede ejecutar un hilo y puede obtener el resultado de su cálculo. Una continuación es un hilo que puede copiar, por lo que puede ejecutar el mismo cálculo dos veces.

1

Se ha renovado el interés en la programación web porque reflejan muy bien el carácter de pausa/reanudación de las solicitudes web. Un servidor puede construir una continaution que represente una sesión de usuarios y reanudar si y cuando el usuario continúa la sesión.

Cuestiones relacionadas