Прежде всего, рассмотрим понятие состав .Мы можем легко выразить композицию как операцию над делегатами:
public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
return x => f(g(x));
}
Так что, если у меня есть функция g, равная (int x) => x.ToString()
, и функция f, равная (string s) => s.Length
, я могу создать составную функцию h, котораяэто (int x) => x.ToString().Length
, вызывая f.Compose(g)
.
Это должно быть ясно.
Теперь предположим, что у меня есть функция g от T
до Monad<U>
и функция f от U
до Monad<V>
.Я хочу написать метод, который объединяет эти две функции, которые возвращают монады в функцию, которая принимает T
и возвращает Monad<V>
.Поэтому я пытаюсь написать, что:
public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
return x => f(g(x));
}
Не работает.g
возвращает Monad<U>
, но f
занимает U
.У меня есть способ «обернуть» U
в Monad<U>
, но у меня нет способа «развернуть» его.
Однако, если у меня есть метод
public static Monad<V> Bind<U, V>(this Monad<U> m, Func<U, Monad<V>> k)
{ whatever }
тогда я могу написать метод, который объединяет два метода, которые оба возвращают монады:
public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
return x => Bind(g(x), f);
}
Именно поэтому Bind принимает функцию от T
до Monad<U>
- потому чтовесь смысл в том, чтобы иметь возможность взять функцию g от T
до Monad<U>
и функцию f от U
до Monad<V>
и объединить их в функцию h от T
до Monad<V>
,
Если вы хотите взять функцию g с T
до U
и функцию f с U
до Monad<V>
, тогда вам не нужно Bind в первую очередь .Просто составьте функции обычно , чтобы получить метод от T
до Monad<V>
!Вся цель Bind - решить эту проблему;если вы отмахнетесь от этой проблемы, вам не понадобится Bind.
UPDATE:
В большинстве случаев я хочу составить функцию g с T
до Monad<U>
и функция f от U
до V
.
И я полагаю, что затем вы захотите скомпоновать это в функцию от T
до V
.Но вы не можете гарантировать, что такая операция определена!Например, возьмите «Монаду Может быть» в качестве монады, которая выражается в C # как T?
.Предположим, у вас есть g как (int x)=>(double?)null
, и у вас есть функция f, которая равна (double y)=>(decimal)y
.Как вы должны составить f и g в метод, который принимает int и возвращает ненулевой тип decimal
?Не существует «развёртывания», которое разворачивает обнуляемый double в двойное значение, которое может принимать f!
Вы можете использовать Bind для объединения f и g в метод, который принимает int и возвращает обнуляемый десятичный знак:
public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, V> f, Func<T, Monad<U>> g)
{
return x => Bind(g(x), x=>Unit(f(x)));
}
, где Unit - это функция, которая принимает V
и возвращает Monad<V>
.
Но просто нет композиции f и g, если g возвращает монаду, а f не возвращает монаду - нет никакой гарантии, что существует способ вернуться от экземпляра монады к«развернутый» тип.Может быть, в случае некоторых монад всегда есть - например, Lazy<T>
.Или, может быть, иногда есть, как с «возможно» монадой.Часто есть способ сделать это, но нет требования , чтобы вы могли это сделать.
Кстати, обратите внимание, как мы только что использовали «Bind» в качестве швейцарского армейского ножа, чтобы сделатьновый вид композиции.Привязка может сделать любую операцию!Например, предположим, что у нас есть операция Bind для монады последовательностей, которую мы называем «SelectMany» для типа IEnumerable<T>
в C #:
static IEnumerable<V> SelectMany<U, V>(this IEnumerable<U> sequence, Func<U, IEnumerable<V>> f)
{
foreach(U u in sequence)
foreach(V v in f(u))
yield return v;
}
У вас также может быть оператор для последовательностей:
static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
foreach(A item in sequence)
if (predicate(item))
yield return item;
}
Вам действительно нужно написать этот код внутри Where
?Нет!Вместо этого вы можете создать его полностью из «Bind / SelectMany»:
static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
return sequence.SelectMany((A a)=>predicate(a) ? new A[] { a } : new A[] { } );
}
Эффективно?Нет. Но Bind / SelectMany ничего не может сделать.Если вы действительно этого хотите, вы можете создать все операторы последовательности LINQ только из SelectMany.