CURRY CON C# 3.0

 

Leyendo sobre programación funcional en C#, me topé con el concepto de "Curry". En este caso no se trata de una comida de la India. En programación es, en resumen, hacer que una función tome otra como valor de retorno. El término "Curry" existe en honor a Haskell Curry.

Para este tutorial copié el primer ejemplo que daba Dustin Campbell y lo probé en el Visual Studio 2008. 

Su código es bastante sencillo: dos métodos anónimos, uno dentro del otro. El método "add" retorna un valor del tipo "CurriedOperation2", éste es cualquier método anónimo declarado dentro del cuerpo de "add". Cuando se aplica curry, el valor de retorno siempre debe ser una función o un método.

Como era sencillito, le cambié un poco la forma para hacerlo recursivo y después de jugar un poco en el Visual Studio pude lograrlo. La mejor manera es guiarse con los valores de retorno de los métodos. Tanto en C como en C#, el tipo de valor de retorno o valor devuelto va antes del nombre de la función, en C# si es un método, va justo después de la palabra "delegate".

Al final éste fue mi código:

using System;

using System.Text;

namespace Curry_Prueba

{

class Program

{

delegate CurriedOperation2 CurriedOperation(int x);

delegate int CurriedOperation2(CurriedOperation2 C, int y);

static void Main(string[] args)

{

CurriedOperation2 C;

CurriedOperation add = delegate(int x)

{

C = (c, n) => {

if (n > 10)

{

Console.WriteLine(n);

return 0;

}

else

{

Console.WriteLine(n);

return c(c, n + 1);

}

};

return C;

};

C = null;

add(13)(C,-5);

//Console.WriteLine(add(13)(C,1));

Console.Read();

}

}

}


El método "add" es del tipo "CurriedOperation" y devuelve un "CurriedOperation2" pues "add" es la función donde se aplica curry (es la función que devuelve otra función), en cambio la función recursiva "C" tipo "CurriedOperation2" devuelve un valor del tipo int (no es lo mismo que una función sea "CurriedOperation2" a que devuelva un "CurriedOperation2"). La función "C" recibirá dos parámetros: la propia función "C" del tipo CurriedOperation2 y un valor int, que dará la condición de parada. 

La función "C" es una función lambda, y se declara fuera de "add" para poder llamarla al final. El compilador exige asignarle un valor, así que primero se hace: C=null. 

Este es el resultado:

 

La variables "x" anda sobrando porque no la uso, y la línea Console.WriteLine(n) puede sacarse del if...else. También hice que "add" sea otra función lambda pero esta vez no recibe ningún parámetro (por ello se dejan los dos paréntesis vacíos al declarar "add", ahí van los parámetros de la función, y esta vez no se recibe ninguno):

using System;

using System.Text;

namespace Curry_Prueba

{

class Program

{

delegate CurriedOperation2 CurriedOperation();

delegate int CurriedOperation2(CurriedOperation2 C, int y);

static void Main(string[] args)

{

CurriedOperation2 C;

CurriedOperation add = ()=> {

C = (c, n) => {

Console.WriteLine(n);

return n > 10 ? 0: c(c, n + 1); };

return C;

};

C = null;

add()(C,-5);

Console.Read();

}

}

}

El resultado es el mismo que en el caso anterior, pero con un código más cortito.

Existe una manera para acortar aún más este código y es usando la palabra reservada "Func" del C# 3.0.  La sintaxis es:

Func<valor devuelto>

Func<parámetro, valor devuelto>

Func<parámetro, parámetro, ... valor devuelto>

 

Una mejor manera de entenderlo es con esto:

Las figuritas representan cualquier tipo (int, bool, double, etc), pero siempre el valor de retorno es el que está más a la derecha, en el lugar de la estrellita. El compilador permite hasta cuatro parámetros y un valor de retorno. Al usar "Func" se debe retornar obligatoriamente un valor (siempre debe haber una estrellita).

Con una función lambda "Xxx" cualquiera, que recibe dos parámetros y devuelve un valor (el cual ya se asigna automáticamente a "Xxx"), queda:

 

Y esta es la forma que se le va a dar a "add" convertido ahora en función lambda con curry:

using System;

using System.Text;

namespace Curry_Prueba

{

class Program

{

delegate int CurriedOperation2(CurriedOperation2 C, int y);

static void Main(string[] args)

{

CurriedOperation2 C;

Func<CurriedOperation2> add = ()=> {

C = (c, n) => {

Console.WriteLine(n);

return n > 10 ? 0: c(c, n + 1); };

return C;

};

C = null;

add()(C,-5);

Console.Read();

}

}

}


Es necesario declarar "C" fuera del cuerpo de "add" para poder tener acceso a "C" desde fuera de "add", esto es debido al principio de "Variable Scope".

Aquí hay más para leer acerca del Curry:

http://gregbeech.com/blogs/tech/archive/2008/09/16/function-currying-in-c.aspx

http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

http://www.c-sharpcorner.com/UploadFile/rmcochran/Curry01122008102239AM/Curry.aspx

Y acá un poco más sobre las funciones lambda:

http://www.c-sharpcorner.com/UploadFile/dhananjaycoder/lambdaexpressioninc07032009061441AM/lambdaexpressioninc.aspx

http://www.c-sharpcorner.com/UploadFile/udeshikah/104012008021038AM/1.aspx

 

Actualización :)

Releyendo la recursividad anónima de Ian Marteens, hallé la forma de acortar aún más el código del curry recursivo. Leyendo este código suyo:

Noté que se puede prescindir del uso de los delegados (delegates) y acortar aún más la función del curry. "Func" puede aceptar como parámetros y valor de retorno otros "Func". Haciendo que la función "C" tome la forma de la función "factorial" de Ian, el código del curry sería:

using System;

using System.Text;

namespace Curry_Prueba

{

class Program

{

static void Main(string[] args)

{

Func<int,int> C= null;

Func<Func<int, int>> add = () =>

{

return C = n =>

{

Console.WriteLine(n);

return n > 10 ? 0 : C(n + 1);

};

};

add()(-5);

Console.Read();

}

}

}

Pero en este caso no es necesario poner la palabra "return" antes de "C". Quitando los paréntesis (la función "add" se compone de una sola expresión: la función "C") el valor de retorno es lo que va inmediatamente después del símbolo de asignación "=>". El código finalmente queda:

using System;

using System.Text;

namespace Curry_Prueba

{

class Program

{

static void Main(string[] args)

{

Func<int,int> C= null;

Func<Func<int, int>> add = () =>

C = n =>

{

Console.WriteLine(n);

return n > 10 ? 0 : C(n + 1);

};

add()(-5);

Console.Read();

}

}

}