Por fin entiendo qué son las mónadas

Anuncio
Por fin entiendo qué son las mónadas
using std::cpp 2014
Joaquín Mª López Muñoz <joaquin@tid.es>
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 <joaquin@tid.es>
Madrid, octubre 2014
Descargar