
Em um artigo anterior, discutimos por que a programação funcional não é nada do que é PR e que ela não contradiz o OOP, de modo que até Fowler escreve sobre um bom design de FP que gera um bom design de programa de OOP (e vice-versa).
Agora, quero lhe dizer o que realmente são as mônadas , como elas são úteis para um desenvolvedor praticante comum e darei exemplos de por que o suporte insuficiente em idiomas comuns leva a copiar e colar e soluções não confiáveis.
Mas existem literalmente centenas de artigos na Internet sobre FP e mônadas na Internet, por que escrever outro?
, ( ) : , T , T- — . , , — , , , , , … - .
, , , : , .
, , , : , , , , , . .
, ? : , , , . , , , , , .
, , . C#. Scala , Rust , , Haskell .
, C# 10 ( ), Shapes HKT. (aka , aka ). , ,
public shape JsonDeserialize<T>
{
static T Deserialize(JObject input);
}
JsonSerializationException: Could not create an instance . , .
: -, ( ), , -, . , ICollection<T> IReadOnlyCollection<T>, . , . . - - where T : Number, , , : Number .
- . , :
public static T<int> CreateSomethingOfInts<T>() where T : <>, new()
{
return new T<int>();
}
, , , :
//
var list = CreateSomethingOfInts<List>();
var hashSet = CreateSomethingOfInts<HashSet>();
// …
// Array<T>, Nullable<T>, LinkedList<T>, ... -
// Array<int>, Nullable<int>, LinkedList<int>, ...
C# 8. , , . , .
, , .
Functor
— . " , !" — . , — , , .
, ? , , — , Functor :
public shape Functor<T> where T : <>
{
static T<B> Map<A, B>(T<A> source, Func<A, B> mapFunc);
}
, . , " , ". , Mappable, , , Map, -, A, , B.
, ToString, . , . , , -. — , - . A => B, . Map , , A, B.
Map : mapFunc Identity- x => x, . , "" , :
Map(something, x => x) === something
, , , , , mapFunc. , , , Map .
?
, , :
public extension EnumerableFunctor of IEnumerable : Functor<IEnumerable>
{
public static IEnumerable<B> Map<A, B>(IEnumerable<A> source, Func<A, B> map) =>
source.Select(map);
}
//
var range = Enumerable.Range(1, 10);
Console.WriteLine(Map(range, x => x).SequenceEquals(x)) // True
, , ! , IEnumerable — ,
? , 100 .
Nullable. :
public extension NullableFunctor of Nullable : Functor<Nullable>
{
public static B? Map<A, B>(A? source, Func<A, B> map) =>
source is A notNullSource ? map(notNullSource) : default(B?);
}
//
int? nullableTen = 10;
int? nullableNull = null;
Console.WriteLine(Map(nullableTen, x => x) == nullableTen); // True
Console.WriteLine(Map(nullableNull, x => x) == nullableNull); // True
, Nullable — .
— Task, Map .
, : , . , " ". , , , .

Map , ,
— ! ...
Applicative
! , :
public shape Applicative<T> where T : <>
{
static T<A> Pure<A>(A a);
static T<C> LiftA2<A, B, C>(T<A> ta, T<B> tb, Func<A,B,C> map2);
}
? , , :
- .
where T : new(), , new() , . - . ,
T<A> T<B> , A, B C, T<C>. LiftA2 , "" A B T<A> T<B> .
? . - – - . , , , - ?
"Talk is cheap, show me the code", , , . — , , - — . :
public extension EnumerableApplicative of IEnumerable : Applicative<IEnumerable>
{
static IEnumerable<A> Pure<A>(A a) => new[] { a };
static IEnumerable<C> LiftA2<A, B, C>(IEnumerable<A> ta,
IEnumerable<B> tb,
Func<A, B, C> map2) =>
ta.SelectMany(a => tb.Select(b => map2(a, b)));
}
, . — , public extension. - , , ( ).
Nullable? :
public extension NullableApplicative of IEnumerable : Applicative<Nullable>
{
static A? Pure<A>(A a) => a;
static C? LiftA2<A, B, C>(A? ta, B? tb, Func<A, B, C> map2) =>
(ta, tb) switch {
(A a, B b) => map2(a, b), // null -
_ => default(C?) // - - null
};
}
" ", , " , , ...". , , , . ZipList:
public class ZipList<T> : IEnumerable<T>
{
private IEnumerable<T> _inner;
// .. IEnumerable<T>
}
public extension ZipListApplicative of ZipList : Applicative<IEnumerable>
{
static IEnumerable<A> Pure<A>(A a) =>
// 'a'
Enumerable.Repeat(a, int.MaxValue);
static IEnumerable<C> LiftA2<A, B, C>(IEnumerable<A> ta,
IEnumerable<B> tb,
Func<A, B, C> map2) =>
ta.Zip(tb, map2);
}
, ZipList , . Applicative , Zip.
, , - ? ? , — - , - , , - . , , , : (F<A>, F<B>) -> F<(A, B)>. :
public static T<(A, B)> Combine(T<A> ta, T<B> tb) =>
LiftA2(ta, tb, (a, b) => (a, b));
var eta = Enumerable.Range(3, 2);
var etb = Enumerable.Range(15, 4);
int? nta = 10;
int? ntb = null;
Combine(eta, etb) // [(3, 15), (3, 16), (3, 17), (3, 18), (4, 15), (4, 16), (4, 17), (4, 18)]
Combine(nta, nta) // (10, 10)
Combine(nta, ntb) // Null
Combine(new ZipList<int>(eta), new ZipList<int>(etb)) // [(3, 15), (4, 16)]
, , . — , Combine, , . , , , null. ZipList , . , ( (a, b) => (a, b)) ( T<>). , , , ( /// ), , , , .
, , , , . , , , " ". , .

pure ,

liftA2 ,
,
, , , " ". ? "" . , , . LiftA2 . , , , null — . - .
? LiftA2 Pure Map:
static T<B> MapAnyFunctor<T, A, B>(T<A> source, Func<A, B> map) where T : Applicative =>
LiftA2(source, Pure(0), (a, _) => map(a));
? — Pure T<>. T<A> T<>, LiftA2. , , map T<A>. , . :
public shape Applicative<T> : Functor<T> where T : <>
{
static T<A> Pure<A>(A a);
static T<C> LiftA2<A, B, C>(T<A> ta, T<B> tb, Func<A,B,C> map2);
}
. , , .
Task<T>:
public extension TaskApplicative of Task: Applicative<Task>
{
public static Task<A> Pure<A>(A a) => Task.FromResult(a);
public static async Task<C> LiftA2<A, B, C>(Task<A> ta, Task<B> tb, Func<A, B, C> map2)
{
await Task.WhenAll(ta, tb);
return map2(ta.Result, tb.Result);
}
}
Monad
. , .
, :
public shape Monad<T> where T : <>
{
static T<A> Pure<A>(A a);
static T<B> Bind<A, B>(T<A> ta, Func<A, T<B>> mapInner);
}
-, :
— Pure Bind, T<A> , A T<B>, T<B>
, . , : . A , B. Bind "" , (T<A>, A => T<B>) T<B>.
— , , :
public extension EnumerableMonad of IEnumerable : Monad<IEnumerable>
{
static IEnumerable<B> Bind<A, B>(IEnumerable<A> ta, Func<A, IEnumerable<B>> mapInner) =>
ta.SelectMany(mapInner);
// Pure
}
, Nullable . ZipList? , , : , , , ( ).
, , . ZipList , .

, Pure : . LiftA2, Pure Bind, .
, , , . , , do-. :
let foo = do
a <- someA
b <- someB
pure (doSomethingWith a b)
Bind :
var foo = Bind(someA, a => Bind(SomeB, b => Pure(DoSomethingWIth(a,b)))
. <- Bind,
, — , .
.
, , .
? . , ?
var values = from x in new []{ 1, 2, 3 }
from y in new []{ 4, 5, 6 }
select x + y;
, do- ( ):
let values = do
x <- [1,2,3]
y <- [4,5,6]
pure (x + y)
:
var maybeA = GetMaybeA();
var maybeB = maybeA?.GetB();
var maybeC = maybeB?.GetC();
var result = maybeC;
do- Maybe ( Option, — Nullable):
let result = do
maybeA <- getMaybeA
maybeB <- getB maybeA
maybeC <- getC maybeB
pure maybeC
?
var valueA = await GetSomeA();
var valueB = await GetSomeB(valueA);
var result = valueB;
do- IO ( , , , Task ):
let result = do
valueA <- getSomeA
valueB <- getSomeB valueA
pure valueB
,
: T<>
A T<B>, T<B>, A[] A -> B[], A? A -> B?,
Task<A> A -> Task<B>,… .
...
, , . , , " -, - ". -, LINQ, null propagation . . nullable-? , - , , - . ? , . , , , - LINQ- (, First ).
, : MapAnyFunctor . async enumerable, - ( , CancallationToken?), ? - , ?
, : List IO.
, , : . Rust , . : try-, Try-, -, -. , async enumerable, , , .
do-, , , , , .
: , -. , , , . , ( , Par)? , , C# 15, , , . .
, " -, -". , Bind. ContinueWith, SelectMany, . , Rust . Option/Result/Future , , : Monad, MonadFail/Bifunctor/..., .
? - . 1.29 flatten , , . , .
transpose Option/Result , , transpose 0.8 2013 . transpose ( IO ) , 7 , .
, : Monad , transpose/flatten/... . , - , , . , 2015 . , , , .
. System.Linq.Async. , LINQ corefx, async-await. , , . , . , - . filterM/mapM/whateverM, //… , ( M ), .
, ,
, Monad ( ) , . , , , . , "" — , , ( ), , , , .
, , , Go . Functor/Applicative/Monad/… . HKT — , , , . , , , . , , .
, , . , , IEnumerable. — . ORM IQueryable, , ORM . - , , ? ?
, . Haskell, My.Big.Corporation.Utils, , " ". , , , , -.
, — , . — , . , " ", , , . — , .
, , , : - . . Akka, . , — , ? , , - , , , . , , , , - , , - . , , . , , , , .

, , , , — . , , - , — , - , , , . — , , — , — , .
. , , : SOLID. ? , , , :
public M<Comment> GetArticleComment<M>(int articleId)
where M : MonadWriter<LogMessage[]>, MonadReader<Config>, MonadHttp<AllowedSite>
MonadWriter/MonadReader/MonadHttp — SOLID , . , ( LogMessage!), ( Config!) Http ( AllowedSite!), .
, , . , , Task. return 10, Task<int>.
, Task M, — , .
, SOLID, — . Autofac/Windsor/Ninject/… " ", where , , . , , , .
, , , .
, , , . : , , - - .
class MyService
{
async Task<Comment> SomeBusinessLogicAsync(int commentId) {
var comment = await this.remoteClient.GetAsync($"some/url/{commentId}");
// .. do stuff ..
return await DoOtherStuffAsync(comment);
}
}
. C# ?
, , MyService remoteClient , .
- , httpClient, ( , ), act/assert .
? , ( ) Id, :
public class Id<T>
{
public T Value { get; }
public Id(T value)
{
Value = value
}
}
public extension IdMonad of Id : Monad<Id>
{
static IdMonad<A> Pure<A>(A a) => new Id<A>(a); //
static IdMonad<B> Bind<A, B>(IdMonad<A> ta, Func<A, IdMonad<B>> mapInner) =>
mapInner(ta.Value); //
// map liftA2 -
}
:
Task<Comment> SomeBusinessLogicAsync(int commentId);
M<Comment> SomeBusinessLogicAsync<M>(int commentId) where M : Monad =>
this.remoteClient.Get($"some/url/{commentId}").Bind(comment =>
// .. do stuff ..
return DoOtherStuff(comment);
);
do- 11, . :
var comment = await myService.SomeBusinessLogicAsync<Task>(547);
:
var comment = myService.SomeBusinessLogicAsync<Id>(547).Value;
! , SOLID ( ). , , , . , . , - . , . . , .
— . , Task.FromResult - . , , . : : , . " ", . , , .
, M , Task Id, , , , ( M ), ( M — Option), ( M — Result), . — . , , , — , . : — , — .

, , . Traversable, Foldable, , , , .
, :
(. Mappable)
: , Map , .
: , T<A> T<B>.
: ;
(, . PairMappable)
: , Pure LiftA2,
( , ).
"".
: T<A> T<B> T<C>.
: (, List, Option, ZipList, ..);
-
(. NestedJoinMappable):
: , Pure Bind ( ).
"".
: , T<B> A,
T<A>
: , ; -
, , ( ) - , ,
, , , , .
Mônadas são apenas uma ferramenta para poder usar. Estudar é um investimento bem-sucedido que salvará mais de um mês de vida, destruindo a causa de muitas perguntas “bem, que diabos isso não funciona, eu verifiquei tudo ” pela raiz. E embora nenhuma técnica de programação o salve de todos os problemas , é um pecado não usar uma ferramenta que resolva uma parte significativa deles.