Монади
Монада складається з
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 може бути. Як майже все інше .