Як співвідносяться Вільна монада та Реактивне розширення?


14

Я походжу з C #, де LINQ перетворювався на Rx.NET, але завжди мав певний інтерес до FP. Після деякого ознайомлення з монадами та деякими побічними проектами у F #, я був готовий спробувати перейти до наступного рівня.

Тепер, після декількох розмов про вільну монаду людей зі Скали та декількох записів у Haskell, або F #, я виявив, що граматики з перекладачами для розуміння є досить схожими на IObservableланцюги.

У FRP ви складаєте визначення операції з менших доменних фрагментів, включаючи побічні ефекти та збої, що залишаються всередині ланцюга, і моделюєте ваше додаток як набір операцій та побічних ефектів. У вільній монаді, якщо я правильно зрозумів, ви робите те саме, роблячи свої операції як функтори, і піднімаючи їх за допомогою coyoneda.

Які будуть різниці між обома, що нахиляють голку до будь-якого з підходів? У чому полягає принципова відмінність визначення вашого сервісу чи програми?


2
Ось цікавий спосіб подумати про проблему ... FRP можна розглядати як монаду, навіть якщо вона зазвичай не формулюється таким чином . Більшість (хоча і не всі) монади ізоморфні вільній монаді . Оскільки Contєдина монада, яку я бачив, припустив, що її неможливо виразити через вільну монаду, можливо, можна припустити, що FRP може бути. Як майже все інше .
Жуль

2
За словами Еріка Мейєра, дизайнера LINQ та Rx.NET, IObservableце примірник монади продовження.
Йорг У Міттаг

1
Зараз у мене немає часу опрацювати деталі, але я гадаю, що як розширення RX, так і підхід до вільної монади досягають дуже схожих цілей, але можуть мати трохи інші структури. Можливо, що RX Observables - це самі монади, і тоді ви можете зіставити вільний обчислення монади на один за допомогою спостережних даних - це дуже приблизно те, що означає "вільний" у "вільній монаді". А може бути, стосунки не такі прямі, і ви просто підбираєте, як вони використовуються для подібних цілей.
Тихон Єлвіс

Відповіді:


6

Монади

Монада складається з

  • 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.


Проникливе розуміння теорії категорій! Я був після чогось, що говорив не про те, як вони створюються, більше про відмінності при побудові функціональної архітектури та моделюванні композиції ефектів з будь-яким з них. FreeMonad можна використовувати для створення DSL для повторних операцій, тоді як IObservables більше стосується дискретних значень у часі.
MLProgrammer-CiM

1
@ MLProgrammer-CiM, я побачу, чи зможу я додати деякі відомості про це в найближчі пару днів.
Натан Девіс

Я б хотів практичний приклад вільних монад
l --'''''--------- '' '' '' '' '' ''
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.