Re[5]: Монады - пример где бы были полезны...
От: IT Россия linq2db.com
Дата: 30.08.19 00:57
Оценка: 7 (5) +1
Здравствуйте, Shmj, Вы писали:

S>Т.е. Linq можно назвать монадой и никто не будет возражать?


Скорее всего будет. Linq синтаксис обеспечивает синтаксическую поддержку монадам, но сам монадой не является.

Монада — это абстракция, позволяющая организовывать цепочки вычислений таким образом, что-бы устранить из них boilerplate code.
Boilerplate code — это повторяющийся от вычисления к вычислению типовой код. Например, в монаде Future это создание и своевременный вызов callback функции, передача результата в следующую цепочку. В Maybe — упаковка, распаковка значения в контейнере, прерывание вычислений, если уже не надо. В List — преобразование двух последовательностей данных в одну. И т.д.

Для реализации монады нужно имплементировать монадный тип, методы return и bind. И тогда, если компилятор поддерживает монады, такой boilerplate code действительно исчезает с глаз.

Linq query comprehension syntax можно использовать для поддержки монад.

Для монады List в C# уже всё есть. Вот её использование в чистом виде.

var cities = new[]
{
    (name:"NYC",    streets: new[] { "1", "2", "3" }),
    (name:"Moscow", streets: new[] { "3", "5", "8" }),
};

var q =
    from c in cities
    from s in c.streets
    select c.name + " " + s;

foreach (var item in q)
{
    Console.WriteLine(item);
}


Монадным типом здесь является IEnumerable<T>. Методом bind метод со следующей сигнатурой:

public static IEnumerable<TResult> SelectMany<TSource,TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector)


Без return можно обойтись.

Если реализовывать монаду Maybe (Option), то в качестве монадного типа подойдёт что-то типа:

abstract class Maybe<T>
{
    public class Nothing : Maybe<T> {}
    public class Just : Maybe<T>
    {
        public T Value;
    }
}


И тогда реализация return и bind (и ещё вспомогальный метод для поддержки linq) будут такими:

// return
public static Maybe<T> ToMaybe<T>(this T value)
{
    return value == null ? (Maybe<T>)new Maybe<T>.Nothing() : new Maybe<T>.Just { Value = value };
}

// bind
public static Maybe<T2> SelectMany<T1,T2>(
    this Maybe<T1> source, Func<T1,Maybe<T2>> selector)
{
    if (source is Maybe<T1>.Just j) return selector(j.Value);
    return new Maybe<T2>.Nothing();
}

// специфика linq - типовой метод для всех монад
public static Maybe<T3> SelectMany<T1,T2,T3>(
    this Maybe<T1> source, Func<T1,Maybe<T2>> collectionSelector, Func<T1,T2,T3> resultSelector)
{
    return source.SelectMany(x => collectionSelector(x).SelectMany(y => resultSelector(x, y).ToMaybe()));
}


Здесь сигнатура метода SelectMany сменила IEnumerable<T> на Maybe<T> и в этом состоит весь прикол. Это такая малоизвестная фича C#.
Теперь можно использовать linq syntax:

var n =
    from c in 1.ToMaybe()
    from s in "2".ToMaybe()
    from x in 2.ToMaybe()
    select s + c + x;

Console.WriteLine(n);


Монаду Future тут уже был, но приведу ещё раз (без return ибо лень):

// bind
public static Task<T2> SelectMany<T1,T2>(
    this Task<T1> source, Func<T1,Task<T2>> selector)
{
    return source.ContinueWith(x => selector(x.Result)).Unwrap();
}

// вспомогательный метод, почти тоже самое что было в Maybe.
public static Task<T3> SelectMany<T1,T2,T3>(
    this Task<T1> source, Func<T1,Task<T2>> collectionSelector, Func<T1,T2,T3> resultSelector)
{
    return source.SelectMany(x => collectionSelector(x).SelectMany(y => resultSelector(x, y).ToTask()));
}


И использование

var t =
    from c in Task.FromResult(1)
    from s in Task.Run(() => "7" + c)
    select s + c;

Console.WriteLine(t.Result);


Всё это делать можно, но на мой взгляд годится только для объяснения что такое монады. Тем более, что большинство нужных монад в C# уже реализовано на уровне языка. А без ненужных мы как-нибудь проживём.
Если нам не помогут, то мы тоже никого не пощадим.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.