Monads sebagai pola penggunaan kembali kode


Dalam artikel sebelumnya, kita membahas mengapa pemrograman fungsional sama sekali tidak seperti PR, dan bahwa itu tidak bertentangan dengan OOP sama sekali, sehingga bahkan Fowler menulis tentang desain FP yang baik yang menghasilkan desain program OOP yang baik (dan sebaliknya).


Sekarang saya ingin memberi tahu Anda apa sebenarnya monad itu , bagaimana monad itu berguna bagi pengembang praktik biasa, dan saya akan memberikan contoh mengapa dukungan yang tidak memadai dalam bahasa umum mengarah pada solusi salin-tempel dan tidak dapat diandalkan.


Tetapi ada ratusan artikel di Internet tentang FP dan monad di Internet, mengapa menulis yang lain?


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


    : , ; -



, , ( ) - , ,


  • ( )
  • ,

, , , , .


Monads hanyalah alat untuk dapat digunakan. Mempelajari itu adalah investasi yang berhasil yang akan menyelamatkan lebih dari satu bulan kehidupan, menghancurkan penyebab banyak pertanyaan β€œbaiklah, APA yang tidak berhasil, saya periksa semuanya ” sejak awal. Dan meskipun tidak ada teknik pemrograman yang akan menyelamatkan Anda dari semua masalah , itu adalah dosa untuk tidak menggunakan alat yang memecahkan sebagian besar dari mereka.


All Articles