
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.