Monaden als Code-Wiederverwendungsmuster


In einem frĂŒheren Artikel haben wir diskutiert, warum funktionale Programmierung ĂŒberhaupt nicht PR ist und dass sie OOP ĂŒberhaupt nicht widerspricht, so dass sogar Fowler ĂŒber ein gutes FP-Design schreibt , das ein gutes OOP-Programmdesign erzeugt (und umgekehrt).


Jetzt möchte ich Ihnen sagen, was Monaden wirklich sind , wie sie fĂŒr einen gewöhnlichen Entwickler nĂŒtzlich sind, und ich werde Beispiele geben, warum unzureichende UnterstĂŒtzung in gĂ€ngigen Sprachen zu Copy-Paste und unzuverlĂ€ssigen Lösungen fĂŒhrt.


Aber es gibt buchstĂ€blich Hunderte von Artikeln im Internet ĂŒber FP und Monaden im Internet. Warum noch einen schreiben?


, ( ) : , 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);
}

? , , :


  1. . where T : new(), , new() , .
  2. . , 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>


    : , ; -



, , ( ) - , ,


  • ( )
  • ,

, , , , .


Monaden sind nur ein Werkzeug, um sie verwenden zu können. Das Studium ist eine erfolgreiche Investition, die mehr als einen Monat Leben rettet und die Ursache vieler Fragen „Nun, was zum Teufel funktioniert es nicht, ich habe alles ĂŒberprĂŒft“ im Keim zerstört. Und obwohl keine Programmiertechnik Sie vor allen Problemen bewahrt , ist es eine SĂŒnde, kein Werkzeug zu verwenden, das einen wesentlichen Teil davon löst.


All Articles