Здравствуйте, 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# уже реализовано на уровне языка. А без ненужных мы как-нибудь проживём.