Por fin entiendo qué son las mónadas - using std::cpp · Por fin entiendo qué son las mónadas ....

Post on 05-Oct-2020

6 views 0 download

Transcript of Por fin entiendo qué son las mónadas - using std::cpp · Por fin entiendo qué son las mónadas ....

Telefónica Digital – Video Products Definition & Strategy

using std::cpp 2014

Joaquín Mª López Muñoz <joaquin@tid.es> Madrid, octubre 2014

Por fin entiendo qué son las mónadas

No es esto…

Tampoco esto…

¡Son las mónadas!

Definición formal

Definición formal

Pero, ¿qué es realmente una mónada?

Una cinta transportadora

Un overload del operador “;”

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…

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

𝒇 𝒙 =𝟏

arcsin( 𝒙)

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++)

optional<double> f(double x) { return inv(arcsin(sqr(x)); } optional<double> f(double x) { return (sqr(x)>>=arcsin)>>=inv; }

>>= es un adaptador activo

x arcsin inv

>>= sqr x

arcsin

>>=

inv

sqr

optional<double> f(double x) { return ((optional<double>(x)>>=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

>>= x

arcsin

>>=

inv

>>=

sqr

unit

optional<double> f(double x) { auto g=[](double y){return arcsin(y)>>=inv;}; return sqr(x)>>=g; }

bind es “asociativo”

Un poco de Lego

>>= sqr x

arcsin

inv

>>=

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)))); }

Por cierto, esta mónada es comúnmente conocida como Maybe Monad

Un poco de Lego

sqr

>>=

arcsin

>>=

inv

>>=

x unit

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

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

¿Qué es una mónada? Mi modesto intento de definición

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); }

Algo más complicado: histogramas

1 ******************** 2 ******************** 3 ******************** 4 ******************** 5 ******************** 6 ********************

Composición de histogramas ~ probabilidad condicionada

1

0.2

2

0.4

3

0.4

A

0.2

B

0.3

C

0.5

A

0.0

B

0.6

C

0.4

A

0.25

B

0.5

C

0.25

A

0.14

B

0.5

C

0.36

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; }

Estrictamente hablando, histogram<T> es una mónada

restringida

Composición de histogramas ~ >>=

1 ************************************************* 2 **************************** 3 ******************* 4 ************ 5 ******* 6 ***

int main() { auto h1=dice(6); auto h2=dice(4); auto h3= h1>>=[&](int x){ return h2>>=[&](int y){ return histogram<int>(x+y); }; }; 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í…

Suma de experimentos

2 ***** 3 ********** 4 *************** 5 ******************** 6 ******************** 7 ******************** 8 *************** 9 ********** 10 *****

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"; }

Lifting monádico

2 ***** 3 ********** 4 *************** 5 ******************** 6 ******************** 7 ******************** 8 *************** 9 ********** 10 ***** -------------------------- 7 none

Intersección de líneas captura / closure

Lifting monádico con argumentos variádicos: http://tinyurl.com/mlifting

La implementación no es trivial

Diagrama de flujo

>>=

+ unit

m2

>>= m1

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"; }

str_size, times_10, to_str no devuelven valores monádicos

De hecho, then se implementa en función de >>=

¿Tanto lío para llamar unas cuantas funciones una tras otra?

El esquema es familiar, ¿no?

then hello then then

times_10 str_size to_str

then

str_size

then >>=

f f

yarn

yarn

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 <joaquin@tid.es> Madrid, octubre 2014