Por fin entiendo qué son las mónadas using std::cpp 2014 Joaquín Mª López Muñoz <[email protected]> Telefónica Digital – Video Products Definition & Strategy Madrid, octubre 2014 No es esto… Tampoco esto… ¡Son las mónadas! Definición formal Definición formal Pero, ¿qué es realmente una mónada? Un overload del operador “;” Una cinta transportadora Una unidad de computación Un truco para introducir efectos laterales en programación funcional Un objeto cuyos métodos devuelven mónadas No es posible definir qué es una mónada Algo parecido a un escritorio Una forma de componer funciones Construyamos una mónada en C++ optional<T> template<typename T> struct optional { optional(T const& x) optional(none_t); T const& get()const; T& get(); operator bool()const; // not really }; optional<double> inv(double x){ if(x==0.0)return none; else return 1.0/x; } optional<double> sqr(double x){ if(x<0.0)return none; else return std::sqrt(x); } optional<double> arcsin(double x){ if(x<-1.0||x>1.0)return none; else return std::asin(x); } Calcular… 𝟏 𝒇 𝒙 = arcsin( 𝒙) La composición directa de funciones no compila optional<double> f(double x) { return inv(arcsin(sqr(x)); } main.cpp: In function 'boost::optional<double> f(double)': main.cpp:34:27: error: cannot convert 'boost::optional<double>' to 'double' for argument '1' to 'boost::optional<double> arcsin(double)' return inv(arcsin(sqr(x)); ^ arcsin acepta doubles, no optional<double>s Semántica deseada: none_t aborta la computación Ahora sí optional<double> f(double x) { auto y=sqr(x); auto z=y?arcsin(y.get()):none; auto w=z?inv(z.get()):none; return w; } ¿Detectas un patrón aquí? Hagámoslo genérico template<typename T,typename F> auto operator>>=(const optional<T>& x,F&& f) { return x?f(x.get()):none; } optional<double> f(double x) { return (sqr(x)>>=arcsin)>>=inv; } No hay nada especial en la elección de “>>=” En C++, de hecho, no es muy buena elección Léase bind (tampoco muy afortunado en C++) >>= es un adaptador activo optional<double> f(double x) { return inv(arcsin(sqr(x)); } x sqr arcsin inv optional<double> f(double x) { return (sqr(x)>>=arcsin)>>=inv; } x sqr >>= >>= arcsin inv Un poco de Lego optional<double> f(double x) { return ((optional<double>(x)>>=sqr)>>=arcsin)>>=inv; } x unit >>= >>= >>= sqr arcsin inv unit acepta un objeto x y devuelve la mónada asociada a x También se lo llama return (confuso en C++) En este ejemplo unit(x) ~ optional<double>(x) Un poco de Lego optional<double> f(double x) { auto g=[](double y){return arcsin(y)>>=inv;}; return sqr(x)>>=g; } x >>= sqr arcsin >>= inv bind es “asociativo” Un poco de Lego optional<double> f(double x) { auto minv=[](const optional<double>& y){return y>>=inv;}; auto marcsin=[](const optional<double>& y){return y>>=arcsin;}; auto msqr=[](const optional<double>& y){return y>>=sqr;}; return minv(marcsin(msqr(optional<double>(x)))); } >>= x >>= unit sqr >>= arcsin inv Por cierto, esta mónada es comúnmente conocida como Maybe Monad Las leyes de la mónada unit : T M<T> >>= : (M<T>, T M<T’>) M<T’> unit(x) >>= f ≡ f(x) m >>= unit ≡ m (m >>= f) >>= g ≡ m >>= λx.(f(x) >>= g) ¿Qué es una mónada? Mi modesto intento de definición ¿Qué es una mónada? Mi modesto intento de definición Una mónada es un patrón de diseño que permite componer funciones con tipos de retorno extendidos Un tipo extendido contiene 0 ó más valores de un tipo básico más cierta semántica/información asociada Algo más complicado: histogramas template<typename T> class histogram { public: histogram(){} histogram(const T& x); // our unit ctor, later on const_iterator begin()const; const_iterator end()const; void add(const T& x,double f); }; histogram<int> dice(int n) { histogram<int> res; for(int i=1;i<=n;++i)res.add(i,1.0/n); return res; } int main() { std::cout<<dice(6); } 1 2 3 4 5 6 ******************** ******************** ******************** ******************** ******************** ******************** Composición de histogramas ~ probabilidad condicionada A 0.2 1 0.2 B 0.3 C 0.5 A 0.0 2 0.4 B 0.6 C 0.4 A 0.25 3 0.4 B 0.5 C 0.25 A 0.14 B 0.5 C 0.36 Composición de histogramas ~ >>= template<typename T,typename F> auto operator>>=(const histogram<T>& x,F&& f) { decltype(f(x.begin()->first)) res; for(const auto& p:x){ for(const auto& q:f(p.first)){ res.add(q.first,q.second*p.second); } } return res; } int main() { auto h=dice(6)>>=dice; std::cout<<h; } 1 2 3 4 5 6 ************************************************* **************************** ******************* ************ ******* *** Estrictamente hablando, histogram<T> es una mónada restringida Suma de experimentos int main() { auto h1=dice(6); auto h2=dice(4); auto h3= h1>>=[&](int x){ return h2>>=[&](int y){ return histogram<int>(x+y); }; }; 2 3 4 5 6 7 8 9 10 ***** ********** *************** ******************** ******************** ******************** *************** ********** ***** std::cout<<h3; } En Haskell, este constructo se implementa con la notación do do x <- dice 6 y <- dice 4 return x+y No tan relevante en un lenguaje imperativo como C++ (por ahora) Pero hay otro concepto más interesante aquí… Lifting monádico Lifting monádico f(T1, … ,Tn) R F(M<T1>, … , M<Tn>) M<R> template<template<typename> class M,typename T1,typename T2> auto operator+(const M<T1>& m1,const M<T2>& m2) { return m1>>=[&](const T1& x){ return m2>>=[&](const T2& y){ return M<decltype(x+y)>(x+y); }; }; } int main() { auto h1=dice(6); auto h2=dice(4); auto h3=h1+h2; std::cout<<h3; std::cout<<"--------------------------\n"; std::cout<<optional<int>(4)+optional<int>(3)<<"\n"; std::cout<<optional<int>(4)+optional<int>(none)<<"\n"; } 2 ***** 3 ********** 4 *************** 5 ******************** 6 ******************** 7 ******************** 8 *************** 9 ********** 10 ***** -------------------------7 none Diagrama de flujo m1 >>= m2 >>= + unit Intersección de líneas captura / closure Lifting monádico con argumentos variádicos: http://tinyurl.com/mlifting La implementación no es trivial Un catálogo de mónadas Un catálogo de mónadas Maybe List I/O State Reader Writer Continuation Continuation Monad (muy por encima) Construcción de un hilo template<typename T,typename R> struct yarn { yarn(const T& x); template<typename F> // F: T->T2 yarn<T2,R> then(F f)const; R run(); }; int str_size(const std::string& str); int times_10(int x); std::string to_str (int x); int main() { auto c1=yarn<std::string,int>("hello"). then(str_size). then(times_10). then(to_str). then(str_size); std::cout<<"running...\n"; std::cout<<c1.run()<<"\n"; } El esquema es familiar, ¿no? hello yarn then then then then str_size times_10 to_str str_size str_size, times_10, to_str no devuelven valores monádicos De hecho, then se implementa en función de >>= then f >>= f yarn ¿Tanto lío para llamar unas cuantas funciones una tras otra? Unas notas antes de partir Unas notas antes de partir Es posible entender las mónadas ¡Veo mónadas por todas partes! Patrón de diseño para la composición de funciones extendidas Haskell: mónadas + do estilo imperativo C++: inversión de control, entornos no imperativos Lifting remplazamiento de valores básicos por valores monádicos Louis, creo que esto puede ser el comienzo de una bella amistad Por fin entiendo qué son las mónadas Gracias github.com/joaquintides/usingstdcpp2014 using std::cpp 2014 Joaquín Mª López Muñoz <[email protected]> Madrid, octubre 2014