Как-то упустил момент, что при десахаризации LINQ применяется SelectMany с дополнительным resultSelector, в результате чего получается последовательная цепочка вызовов .SelectMany, которых никак не избежать.
Ниже можно видеть пример монады Maybe на C#. В первом случае, даже если мы имеем Nothing, цепочка SelectMany будет вся пройдена, во втором случае, мы сразу обрываем вычисления.
Кто-то может объяснить, почему был выбран именно такой подход рассахаривания, почему не как в большинстве функциональных языков (второй вариант)?
Достаточно было просто, чтобы select последний рассахаривался в Map типа:
public static IMaybe<TB> Map<TA, TB>(
this IMaybe<TA> a,
Func<TA, TB> selector)
как в той же Scala, например.
В чем глубокий смысл существующей реализации сахара LINQ?
Собственно код:
using System;
namespace MaybeMonad
{
internal class Program
{
private static void Main()
{
var result = from a in "Hello ".ToMaybe()
from b in Nothing<string>.Instance
from c in "World!".ToMaybe()
from d in " :)".ToMaybe()
select a + b + c + d;
/*
var result = "Hello ".ToMaybe()
.SelectMany(a => Nothing<string>.Instance, (a, b) => new {a, b})
.SelectMany(@t => "World!".ToMaybe(), (@t, c) => new {@t, c})
.SelectMany(@t => " :)".ToMaybe(), (@t, d) => @t.@t.a + @t.@t.b + @t.c + d);
*/
Console.WriteLine("Result: " + result);
Console.WriteLine();
/*
ctor Just<String>
Bind on Just
ctor Nothing<String>
Bind on Nothing
ctor Nothing<<>f__AnonymousType0`2>
Bind on Nothing
ctor Nothing<<>f__AnonymousType1`2>
Bind on Nothing
Result: Nothing
*/
var result2 =
"Hello ".ToMaybe().SelectMany(a =>
Nothing<string>.Instance.SelectMany(b =>
"World!".ToMaybe().SelectMany(c =>
" :)".ToMaybe().Map(d => a + b + c + d))));
Console.WriteLine("Result2: " + result2);
/*
ctor Just<String>
Bind on Just
Bind on Nothing
Result2: Nothing
*/
}
}
public interface IMaybe<T>
{
IMaybe<TR> Bind<TR>(Func<T, IMaybe<TR>> selector);
}
public sealed class Nothing<T> : IMaybe<T>
{
public static readonly Nothing<T> Instance = new Nothing<T>();
private Nothing()
{
Console.WriteLine("ctor Nothing<" + typeof (T).Name + ">");
}
public IMaybe<TR> Bind<TR>(Func<T, IMaybe<TR>> selector)
{
Console.WriteLine("Bind on Nothing");
return Nothing<TR>.Instance;
}
public override string ToString()
{
return "Nothing";
}
}
public sealed class Just<T> : IMaybe<T>
{
private readonly T _value;
public T Value
{
get { return _value; }
}
public Just(T value)
{
Console.WriteLine("ctor Just<" + typeof (T).Name + ">");
_value = value;
}
public IMaybe<TR> Bind<TR>(Func<T, IMaybe<TR>> selector)
{
Console.WriteLine("Bind on Just");
return selector(Value);
}
public override string ToString()
{
return "Just(" + Value + ")";
}
}
internal static class Ext
{
public static IMaybe<T> ToMaybe<T>(this T value)
{
return new Just<T>(value);
}
public static IMaybe<TB> SelectMany<TA, TB>(
this IMaybe<TA> a,
Func<TA, IMaybe<TB>> selector)
{
return a.Bind(selector);
}
public static IMaybe<TR> SelectMany<TA, TB, TR>(
this IMaybe<TA> a,
Func<TA, IMaybe<TB>> selector,
Func<TA, TB, TR> resultSelector)
{
return a.Bind(v => selector(v).Bind(b => resultSelector(v, b).ToMaybe()));
}
public static IMaybe<TB> Map<TA, TB>(
this IMaybe<TA> a,
Func<TA, TB> selector)
{
return a.Bind(v => selector(v).ToMaybe());
}
}
}
Здравствуйте, Димчанский, Вы писали:
Д>В чем глубокий смысл существующей реализации сахара LINQ?
В том, что
иногда банан — это просто банан SelectMany никогда не предназначался для натягивания монад на шарп.
Конкретно про SelectMany можно
почитать классика, если говорить про монады вообще — я очень сомневаюсь, что они появятся в шарпе в чистом виде, по крайней мере пока сохраняется общая линия развития языка — это точно.
Все крупные фишки шарпа всегда добавляются как _законченное_ решение под конкретный набор проблем, добавление монад "чтоб было" скорее создаст новые, особых сценариев, которые в рамках шарпа будет удобнее решать через монады тоже нет.