Monáda (funkcionální programování)
Monáda je funktoriální datový typ vybavený dvěma přirozenými transformacemi umožňujícími asociativní skládání operací nad monádami. Formálně je monáda monoidem v kategorii endofunktorů v kategorii typů (v Haskellu kategorie Hask). Obecně je-li tenzorová kategorie (kde je bifunktor a ), monády jsou monoidy v .
Přirozené transformace jsou u každé monády multiplikace a jednotka splňující a . Ve funkcionálním programování se tyto operace zpravidla nazývají join a unit. Příkladem monády je kontinuace, kde je funkcí z do definovaná jako a speciální operace vázání je , přičemž je .
Monády umožňují formulovat kód s vedlejšími efekty tak, aby byl referenčně transparentní, a tedy „čistý“ (ve funkcionálním smyslu). Monády jsou zpravidla generické a jejich druh je .
Z každého endofunktoru lze vytvořit volnou monádu.
Příklad
[editovat | editovat zdroj]Kontinuace v C++ lze reprezentovat (s využitím statického polymorfismu) následující třídou:
template<typename T> class Cont {
std::function<std::any(const std::function<std::any(T)>&)> fn;
public:
Cont(const decltype(fn)& f) : fn(f) {}
Cont(T&& x) : fn([x](auto f) { return f(x); }) {}
std::any operator()(const std::function<std::any(T)>& f) const { return fn(f); }
};
Aby byla Cont funktorem, musíme implementovat fmap nebo bind:
template<template<typename> class M, typename T, typename U> struct Bind;
template<typename T, typename U> struct Bind<Cont,T,U> {
std::function<Cont<U>(T)> f;
auto operator()(const Cont<T>& m) {
return [this,m](auto g) {
return m([this,g](auto x) { return f(x)(g); });
};
}
};
C++ nemá typy vyšších druhů, nicméně pomocí specializace šablon můžeme dosáhnout stejného efektu. Konkrétně můžeme například definovat generické monadické operace fmap a join.
template<template<typename> class M, typename T, typename U> struct Fmap {
std::function<U(T)> f;
auto operator()(const M<T>&) const;
};
template<template<typename> class M, typename T> struct Join {
auto operator()(const M<M<T>>&) const;
};
template<template<typename> class M, typename T, typename U> auto Fmap<M,T,U>::operator()(const M<T>& m) const {
std::cerr << "generic fmap" << std::endl;
return Bind<M,T,U>{[&](auto x) { return M<U>{f(x)}; }}(m);
}
template<template<typename> class M, typename T> auto Join<M,T>::operator()(const M<M<T>>& m) const {
std::cerr << "generic join" << std::endl;
return Bind<M,M<T>,T>{[](M<T> x) { return x; }}(m);
}
Takto koncipovaný kód má tu výhodu, že poskytuje definice monadických funkcí bez ohledu na konkrétní typ. Proto stačí pro konkrétní monadický typ implementovat pouze buď bind, nebo fmap a join, podobně jako v čistě funkcionálních jazycích à la Haskell.