Como se comentó en la primera parte, el combinador Y toma una función g que representa un paso de recursión y devuelve una función p que es el punto fijo de g. Esto del punto fijo no es más que la función recursiva obtenida aplicando g a sí misma infinitas veces. El problema con este planteamiento es que currificar en C++ es difícil. Por esta razón usaremos una versión de g que no esté currificada. El prototipo de esta función sería:
#include <functional> int g(std::function<int(int)> f,int v);El tipo de los objetos función en C++ es muy largo y escribir "std::function" cada vez va a emborronar el código. Usaré una macro para aligerar la notación.
#include <functional> //F(X,Y) es X->Y #define F(X,Y) std::function<Y(X)> //F(X,Y,Z) es (X,Y)->Z #define F2(X,Y,Z) std::function≶Z(X,Y)> int g(F(int,int) f,int v);Ahora vamos a tratar de definir el combinador Y. La mejor forma de hacerlo es, en vez de usar las funciones lambda de C++ que son muy tediosas, usando la ecuación recursiva que obtuvimos en la primera parte.
Y(g)=g(Y(g))
Como ahora tengo a g sin currificar, he de añadir un argumento extra. Queda así:
Y(g)=[](x){g(Y(g),x)}
Entonces, el código del combinador Y para los enteros sería
F(int,int) Y( F2( F(int,int) ,int,int) f) { return [=](int x){return f(Y(f),x);}; }
Realmente la parte difícil es la de los tipos, aunque se pueden obtener poco a poco estudiando la expresión.
Cambiar los int por un tipo genérico nos da el combinador Y genérico en C++:
template<typename T> F(T,T) Y( F2( F(T,T) ,T,T) f) { return [=](T x){return f(Y(f),x);}; }
Ahora sólo tenemos que usarlo. Definimos para eso la función g del factorial y lo volcamos a la salida. El código final es el que sigue:
#include <functional> #include <iostream> //F(X,Y) es X->Y #define F(X,Y) std::function<Y(X)> //F(X,Y,Z) es (X,Y)->Z #define F2(X,Y,Z) std::function≶Z(X,Y)> int g(F(int,int) f,int v) { return v==0 ? 1 : v * f(v-1); } template<typename T> F(T,T) Y( F2( F(T,T) ,T,T) f) { return [=](T x){return f(Y(f),x);}; } int main(int argc,char** argv) { std::cout << Y<int>(g)(5) << std::endl; return 0; }
En este punto sólo queda advertir al lector que es necesario usar un compilador compatible con el estándar de C++ de 2011.
0 comentarios:
Publicar un comentario