domingo, 15 de enero de 2012

Celtic Punk y Curryfication (O javascript para programadores funcionales - Parte 4)

Bueno bueno, aca estoy escuchando Floggin Molly (chivo de mi otro blog) y pensando en como traer la curryficacion y la aplicacion parcial a este blog :D y seguir con el thread cuyo post anterior es: highorder functions)

Bueno, primero empecemos por el principio.

Aplicacion Parcial de funciones

Este tema sale por ahí de la pregunta ¿Cual es el resultado de aplicar una funcion de n parametros con n-m parametros?

Por ejemplo:


function suma (a, b) {
        return a + b;
}



Que pasa si yo hago


var a = suma (1);



?


Lo que va a pasar en javascript asi como viene es que va a devolver NaN por resultado.
Va a tratar de sumar 1 + nulll.

O sea, va a tratar de ejecutar el código con lo que tiene. Eso nos lleva un poco a un post anterior (no el de brownies, al de lazy evaluation ), donde decimos "Las cosas se ejecutan cuando es necesario", en este caso agregamos "Y cuando pueden ser ejecutadas".


En el mundo funcional,

var fn = suma(1);


fn es una funcion que recibe un parametro y le suma 1 :) Obvio no?

Ahora, ¿Para qué mierda sirve esto? Hay quien dice que no sirve, que es pizarron, y otras cosas relacionadas

A eso le digo, si, C tambien era "pizarron" en la época de assembler. Objetos pensado tambien es pizarron hoy. Incluso detenerse y pensar antes de codear es "pizarron" desde alguna óptica. So, asi como hay codemonkeys que se contentan con tirar lineas de código por metro, tambien hay quien se contenta con tirar lineas eficientes y pensadas y no lidiar con repetición de código y otros tantos anti-patterns.

    A fin de poder hacer una herramienta poderosa del pase de funciones por parametro (highorder functions), necesitamos herramientas que nos dejen crear funciones a partir de funciones, y dar un saltito chiquito a otro orden de abstracción.

    'Dame un ejemplo práctico'
    'Bueno, ok. JQuery, en sus llamadas a ajax recibe para el caso de exito una funcion que recibe un parametro. Si vos tenes una función de dos parametros que recibe un parametro conocido y el otro es la data que retorna el server tenes que hacer otra función que reciba los datos y hardcodee el dato conocido, si pudieses hacer success:funcionLoca("valorHardCodeado") te ahorrarias multiples referencias indirectas a una función y repetición de código a lo pavote. Por otro lado tendrías un mecanismo para tener funciones polimorfisables.'
    'Aa, pero es un sintax sugar'
    'Si, igual que la suma. Todos sabemos que un programa que suma dos numeros luce así:



suma:
mov al,[1001]
mov bl,[1002]
mov cl,[1003]
add  al,bl
add al,cl
mov [1005],al



   ... digamos que yo prefiero llamarlo bootstraping '

   Como hacemos para tener aplicacion parcial entonces?

   bueno, un esbozo puede ser




var slice = Array.prototype.slice;

function partialApplication ( fn ) {
  var args = slice.apply(arguments, [1]);

  //aplico slice sobre arguments (que es una variable que apunta a todos los argumentos recibidos),
  //que no es de tipo array, pero se le parece bastante


   return function () {
               fn.apply(null, args.concat(slice.apply(arguments));
              // este es un truquito que une args (que esta vez es un array por haber pasado por slice) con

             // arguments pasado por slice para ser tambien un array.
    }

}





Excelente :D Ahora podemos hacer


var suma1 = partialApplication (suma, 1);



  Tadaaaa, aca tenemos una funcion directamente basada en otra :D. Si leen el código van a ver que une n parametros con m parametros. O sea, podrian hacer



var suma12 = partialApplication (suma, 1, 2);

suma12(); // = 3
suma123 = partialApplication (suma, 1, 2);
suma123(); // = 3 - ok, no tiene sentido, pero se puede hacer.




Con esto entonces tenemos una herramienta mas para trabajar con funciones. :)

" Pero pero, para! dijiste algo de curryficacion.. que onda? "

 Curryficacion

 Cierto cierto, no me habia olvidado. La curryficacion viene de (nombre inspirado en Haskell Curry, tambien encontrable como curryfication, currying ) 

 Bueno, esta idea esta muy emparentada con la aplicación parcial. Se trata de ver a las funciones que reciben n parametros (n > 0) como funciones que reciben un parametro y devuelven una funcion n-1 parametros. Cuando la funcion tiene todos los parametros que necesita se aplica o reduce.

entonces

var unoMasTres = suma(1)(2);



Me debiera devolver 3.

Esto nos lleva a la implementación de curryficate, una funcion que dada una funcion devuelve su funcion curryficable. (una aplicación parcial mas prolija y capa)




var slice = Array.prototype.slice;

function curryficate (fn) {

      if(!fn.hasOwnProperty("protoLength")) {
            fn.protoLength = function () { return fn.length; }       }
      var fnwrap = function () {
            var args = slice.apply(arguments,[0]);            if (fn.protoLength() - args.length <= 0) {                  return fn.apply(null, args);            }            else {
                  var curryfied = function () {                        return fn.apply (null, args.concat(slice.apply(arguments,[0])))                  };
            curryfied.protoLength = function () { return fn.protoLength() - args.length;       };
      return curryficate(curryfied);            }
      }

      fnwrap.protoLength = function () { return fn.protoLength() };

      return fnwrap;
}





Es una función un poco mas complicada, dado que una función que no define la cantidad de parametros en la firma no tiene parametros, hay que agregar datos a la funcion (aprovechando que las funciones son objetos :3 .. amo javascript :D )


Como la usamos? las formas mas comodas que encontre son asi:



var foldr = <b>curryficate</b>(
      function(list, funct, aux) {            for(i in list) {                  aux = funct(list[i], aux);            }            return aux;      });



En este caso no nos importa la version no curryficada, entonces definimos la funcion como curryficada de movida.

Por otro lado la otra version es



function foldr(list, funct, aux) {
      for(i in list) {            aux = funct(list[i], aux);      }      return aux;
}
var cfoldr = <b>curryficate</b> (foldr);







Lo que no recomiendo es usar esto en sub-scopes, presta a confusión.

Una vez curryficada la funcion se puede se puede usar de muchas formas:




var suma3 = curryficate ( function (a,b,c) { return a+b+c }}) ;


suma3(1,2,3); // = 6
suma3(1)(2,3); // = 6
suma3(1,2)(3); //= 6
suma3 (1) // = suma3(b,c);
suma3 (1,2) //= suma3(c);




La unica contra de esto es que las funciones curryficadas SOLO ejecutan cuando tienen todos los parametros, por lo que se pierden las funciones de parametros variables. Con esto, curryficate y partialApplication son funciones en parte complementarias.


Felicidad. Disfruten lo que queda de este domingo hermoso :D. Salgan al sol :D

No hay comentarios:

Publicar un comentario