Монады
Монада состоит из
Endofunctor . В нашем мире разработки программного обеспечения мы можем сказать, что это соответствует типу данных с одним параметром неограниченного типа. В C # это будет что-то вроде:
class M<T> { ... }
Две операции, определенные для этого типа данных:
return
/ pure
принимает «чистое» значение (т. е. T
значение) и «оборачивает» его в монаду (т. е. производит M<T>
значение). Поскольку return
это зарезервированное ключевое слово в C #, pure
теперь я буду ссылаться на эту операцию. В C # pure
будет метод с подписью, как:
M<T> pure(T v);
bind
/ flatmap
принимает монадическое значение ( M<A>
) и функцию f
. f
принимает чистое значение и возвращает монадическое значение ( M<B>
). Из них bind
выдает новое монадическое значение ( M<B>
). bind
имеет следующую подпись C #:
M<B> bind(M<A> mv, Func<A, M<B>> f);
Кроме того, чтобы быть монадой, pure
и bind
обязаны соблюдать три монадических закона.
Теперь одним из способов моделирования монад в C # было бы создание интерфейса:
interface Monad<M> {
M<T> pure(T v);
M<B> bind(M<A> mv, Func<A, M<B>> f);
}
(Примечание. Для краткости и выразительности я буду использовать некоторые возможности кода в этом ответе.)
Теперь мы можем реализовать монады для конкретных типов данных путем реализации конкретных реализаций Monad<M>
. Например, мы могли бы реализовать следующую монаду для IEnumerable
:
class IEnumerableM implements Monad<IEnumerable> {
IEnumerable<T> pure(T v) {
return (new List<T>(){v}).AsReadOnly();
}
IEnumerable<B> bind(IEnumerable<A> mv, Func<A, IEnumerable<B>> f) {
;; equivalent to mv.SelectMany(f)
return (from a in mv
from b in f(a)
select b);
}
}
(Я целенаправленно использую синтаксис LINQ, чтобы вызвать связь между синтаксисом LINQ и монадами. Но учтите, что мы могли бы заменить запрос LINQ вызовом SelectMany
.)
Теперь мы можем определить монаду для IObservable
? Казалось бы, так:
class IObservableM implements Monad<IObservable> {
IObservable<T> pure(T v){
Observable.Return(v);
}
IObservable<B> bind(IObservable<A> mv, Func<A, IObservable<B>> f){
mv.SelectMany(f);
}
}
Чтобы убедиться, что у нас есть монада, нам нужно доказать законы монады. Это может быть нетривиально (и я не достаточно знаком с Rx.NET, чтобы знать, можно ли их доказать даже из одной спецификации), но это многообещающее начало. Чтобы облегчить оставшуюся часть этого обсуждения, давайте просто предположим, что в этом случае действуют законы монады.
Бесплатные монады
Не существует единственной «свободной монады». Скорее, свободные монады - это класс монад, которые построены из функторов. То есть, учитывая функтор F
, мы можем автоматически получить монаду F
(то есть свободную монаду F
).
ФУНКТОРЫ
Как и монады, функторы могут быть определены следующими тремя элементами:
- Тип данных, параметризованный над одной переменной неограниченного типа.
Две операции:
pure
оборачивает чистое значение в функтор. Это аналогично pure
для монады. Фактически, для функторов, которые также являются монадами, они должны быть идентичными.
fmap
сопоставляет значения на входе с новыми значениями на выходе с помощью данной функции. Это подпись:
F<B> fmap(Func<A, B> f, F<A> fv)
Как и монады, функторы обязаны подчиняться законам функторов.
Подобно монадам, мы можем моделировать функторы через следующий интерфейс:
interface Functor<F> {
F<T> pure(T v);
F<B> fmap(Func<A, B> f, F<A> fv);
}
Теперь, поскольку монады являются подклассом функторов, мы могли бы также Monad
немного изменить рефакторинг :
interface Monad<M> extends Functor<M> {
M<T> join(M<M<T>> mmv) {
Func<T, T> identity = (x => x);
return mmv.bind(x => x); // identity function
}
M<B> bind(M<A> mv, Func<A, M<B>> f) {
join(fmap(f, mv));
}
}
Здесь я добавил дополнительный метод join
и предоставил реализации по умолчанию для обоих join
и bind
. Обратите внимание, однако, что это круговые определения. Таким образом, вам придется переопределить хотя бы один или другой. Также обратите внимание, что pure
теперь наследуется от Functor
.
IObservable
и бесплатные монады
Теперь, поскольку мы определили монаду для IObservable
и поскольку монады являются подклассом функторов, из этого следует, что мы должны быть в состоянии определить экземпляр функтора для IObservable
. Вот одно определение:
class IObservableF implements Functor<IObservable> {
IObservable<T> pure(T v) {
return Observable.Return(v);
}
IObservable<B> fmap(Func<A, B> f, IObservable<A> fv){
return fv.Select(f);
}
}
Теперь, когда у нас определен функтор IObservable
, мы можем построить свободную монаду из этого функтора. И это именно то, как IObservable
относится к свободным монадам, а именно, из чего мы можем построить свободную монаду IObservable
.
Cont
единственная монада, которую я видел, предполагала, что не может быть выражена через свободную монаду, можно предположить, что FRP может быть. Как и почти все остальное .