hdolder.com srl  

       Software R&D
     hdc Home    |    Contenido    |    KO1    |    Director    |    Direcciones    |    email    |   Twitter   
  M&P TBW - Continuation Passing Style
                                       v190

El concepto de Continuation Passing Style (CPS) fué explicitado en 1975 en relación con el lenguaje Scheme [R1], pero los mecanismos para su implementación estaban presentes en el Lenguaje Lisp (del cual Scheme se deriva) desde 1956.

Los mismos mecanismos están presentes en BScript, el lenguaje de scripting de M&P,  que también es derivado de Lisp [^].

La importancia que el concepto tiene actualmente está relacionada con la programación asincrónica, en la que el CPS es el mecanismo central [R9].

En .NET la implementación del concepto se basa en los "delegates" y/o en elementos relacionados como las "lambda expressions" (lambda passing).

En esta sección suponemos que el lector tiene conocimientos básicos de CPS (existen tutorials en la Web) y nos enfocaremos en los aspectos relacionados con el procesamiento paralelo y la operación Dataflow.

Avance sin retorno

Un concepto central en la operación Dataflow es el de "avance sin retorno": La información fluye unidireccionalmente por la red de componentes que conforma el "dataflow network".

Un "dataflow pipeline" es una forma simple de "dataflow network" en la que los componentes forman parte de una cadena lineal.

Métodos sin retorno

Imaginemos que en el contexto .NET no podamos usar métodos que retornen valores (sólo podemos usar métodos "void"). Es posible desarrollar aplicaciones con esta restricción?  Si, implementando un CPS basado en delegates.

Consideremos un ejemplo elemental para ilustrar el concepto. Vamos a transformar al CPS un método "AddOne" que recibe y devuelve números enteros:

int AddOne(int x)
{
   return x + 1;
}

que utilizamos en forma imperativa (control-flow) como en el siguiente ejemplo supersimplificado:

int y = AddOne(45);
int z = y + 24;

Transformamos el método AddOne agregando un argumento "continuation" cuyo tipo es un delegate Action<int>

void AddOne(int x, Action<int> continuation)
{
   // continuation(x + 1);  /<<< Sync
   continuation.BeginInvoke(x + 1, null, null);  /<<< Async

}

y creamos el método Rest (que encapsula el resto de las sentencias) que satisface el delegate Action<int>

void Rest(int y)
{
   int z = y + 24;
}

y para la invocación usamos ahora:

AddOne(45, Rest);

El resultado es equivalente a pesar de las diferencias notacionales. Sin embargo, como veremos luego, la transformación nos a llevado del dominio "control-flow" al dominio "dataflow".

Transformación de Dominio

Como ha sido la transformación?  Hemos realizado tres cambios:

1. Cambiamos el "return type" a "void".

2. Agregamos un argumento adicional que es un delegate (en este ejemplo de tipo Action<T> donde T es el tipo retornado originalmente por el método).

3. Reemplazamos todas las sentencias "return" por invocaciones al delegate "continuation" (recibido como argumento) con la expresión retornada en la sentencia "return" original.

Pérdidas y Ganancias

La transformación, desde la perspectiva de una mente acostumbrada al procesamiento imperativo control-flow, ha aumentado la cantidad de código, ha complicado la comprensión del código (su flujo de control) y el debugging, y ha aumentado la probabilidad de cometer errores.

En contrapartida la transformación permite ingresar rápidamente al mundo de la programación asincrónica, en la que, como mencionamos antes, el CPS es el mecanismo central. Para una mente acostumbrada al procesamiento asincrónico esta es la manera natural de programar.

 Creemos que la adaptación mental se facilita cuando se comprende el "shift" del dominio control-flow al dominio dataflow, concepto que no   está explicitado en la mayoría de los tutorials.

Por otra parte es importante hacer notar que C#, y también VB, han sido, hasta hace poco tiempo, lenguajes "control-flow oriented" escondiendo gran parte de los mecanismos de soporte  intrínsecos del control-flow tales como el "call stack" y los innumerables "goto".  En la actualidad ambos lenguajes estan evolucionando incorporando nuevas facilidades para el procesamiento asincronico mediante los keywords await y async [R9].

FAQ

La transformación es aplicable a todos los métodos ?

Si, todos los métodos, inclusive los recursivos, pueden ser transformados. Algunos compiladores hacen la transformación internamente para facilitar el análisis orientado a la optimización del código generado.

Si los métodos no retornan que pasa con el "call stack" ?

En una implementación CPS pura el call stack no es necesario. El CPS elimina (o minimiza) el problema del "stack overflow".

Un método puede aceptar varios "continuation" (continuaciones alternativas) ?

Si, adecuando los correspondiente delegates. Las continuaciones alternativas originan datataflow networks. Una aplicación muy interesante de continuaciones alternativas tiene que ver con el Exception Handling usando "error continuations". En síntesis, las continuaciones alternativas permites el control del flujo mediante delegates.

Una continuación se asemeja a un GOTO (que se trata de evitar en la programación control-flow) ?.

Si, y además permiten "non-local gotos". Pero debemos recordar que aunque el desarrollador no utilice "gotos" estos están presentes en el código generado por el compilador.

Si fuere necesario, un método puede abortar completamente una operación terminando sin invocar ninguna continuación ?

Si.

En .NET es posible usar también métodos que retornan valores en un esquema CPS ?

Si, se pueden mezclar libremente los dos tipos de métodos.

Si los compiladores hacen la transformación automáticamente no sería posible avanzar por este camino hacia la "paralelización implícita" ?

Si, pero en general no es conveniente paralelizar todos los métodos porque, como mencionamos en Implicit Parallelism, el paralelismo tiene un costo elevado en términos del Runtime y del Sistema Operativo y un paralelismo de "grano fino" puede ser contraproducente.

En las nuevas versiones de C# y VB se utilizan las keywords async y await (mencionadas previamente) para indicar al compilador las partes que debe paralelizar (tratando de lograr el mayor grado de implicit parallelism) [R3].

El lenguaje Java no soporta pointers (referencias) a métodos. Es posible implementar la programación CPS ?

No.

Ejemplo - Method Dataflow Pipeline

Para fijar el concepto de Dataflow entre métodos, consideremos tres métodos (A, B y C) conectados formando un "dataflow pipeline"

Niveles macro y micro

Este pipeline es análogo al mostrado en Parallel XAML. La diferencia   está en que el pipeline en Parallel XAML está conformado por BLOCKS M&P mientras que el pipeline que describimos en esta sección está conformado por métodos.

El código de los métodos se muestra en el siguiente fragmento:

namespace  cps
{
        delegate  void  DEL(int  val,  object  linkto);

        public  class  CPSBLOCK  :  BLOCK
        {
                public  CPSBLOCK()
                {
                }

                private  int  _PropI  =  -1;    //  <<<<<<<<<<<  
                public  int  PropI
                {
                        set
                        {
                                TestPipeline(value);
                        }
                }

                //  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                void  TestPipeline(int  val)
                {
                        DEL  ADEL  =  new  DEL(A);
                        // ADEL(val,  new  DEL(B));  //<<< Sync
                        ADEL.BeginInvoke(val,  new  DEL(B), null, null);  //<<<  Async
                }

                void  A(int  val,  object  linkto)
                {
                        DEL  BDEL  =  (DEL)linkto;
                        // BDEL(val  +  45,  new  DEL(C));      //<<< Sync 
                        BDEL.BeginInvoke(val +  45,  new  DEL(C), null, null);  //<<<  Async
                }

                void  B(int  val,  object  linkto)
                {
                        DEL  CDEL  =  (DEL)linkto;
                        // CDEL(val  +  55,  null);   //<<< Sync
                        CDEL.BeginInvoke(val  +  55,  null, null, null);  //<<<  Async
                }

                void  C(int  val,  object  linkto)
                {
                        OutS  =  "@"  +  val;
                }

                //  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                //  Output  Property  del  BLOCK
                private  String  _OutS  =  "";
                public  String  OutS
                {
                        get
                        {
                                return  _OutS;
                        }
                        set
                        {
                                if  (_OutS  ==  value)
                                        return;

                                _OutS  =  value;

                                OnPropertyChanged("OutS");
                        }
                }
        }
}

 

Referencias

Ver también

Links sugeridos

 

  TBW The BLOCKS World

©2012 hdolder.com srl  

CVb9I1n3u
2011-12-22