¿Cómo se correlaciona la mónada libre y las extensiones reactivas?

14

Vengo de un fondo de C #, donde LINQ evolucionó a Rx.NET, pero siempre me interesó el FP. Después de una introducción a las mónadas y algunos proyectos paralelos en F #, estaba listo para intentar pasar al siguiente nivel.

Ahora, después de varias charlas sobre la mónada gratuita de gente de Scala y varias revisiones en Haskell o F #, encontré que las gramáticas con intérpretes para la comprensión son bastante similares a las cadenas IObservable .

En FRP, compones una definición de operación a partir de fragmentos más pequeños específicos del dominio, incluidos los efectos secundarios y las fallas que permanecen dentro de la cadena, y modelas tu aplicación como un conjunto de operaciones y efectos secundarios. En la mónada libre, si entendí correctamente, haces lo mismo haciendo tus operaciones como funtores y levantándolos usando coyoneda.

¿Cuáles serían las diferencias entre ambos que inclinan la aguja hacia cualquiera de los enfoques? ¿Cuál es la diferencia fundamental al definir su servicio o programa?

    
pregunta MLProgrammer-CiM 06.06.2016 - 23:54

1 respuesta

5

Mónadas

Una mónada consta de

  • Un endofunctor . En nuestro mundo de ingeniería de software, podemos decir que esto corresponde a un tipo de datos con un único parámetro de tipo no restringido. En C #, esto sería algo de la forma:

    class M<T> { ... }
    
  • Dos operaciones definidas sobre ese tipo de datos:

    • return / pure toma un valor "puro" (es decir, un valor de T ) y lo "envuelve" en la mónada (es decir, produce un valor de M<T> ). Como return es una palabra clave reservada en C #, usaré pure para referirme a esta operación de ahora en adelante. En C #, pure sería un método con una firma como:

      M<T> pure(T v);
      
    • bind / flatmap toma un valor monádico ( M<A> ) y una función f . f toma un valor puro y devuelve un valor monádico ( M<B> ). De estos, bind produce un nuevo valor monádico ( M<B> ). bind tiene la siguiente firma de C #:

      M<B> bind(M<A> mv, Func<A, M<B>> f);
      

Además, para ser una mónada, se requiere que pure y bind obedezcan las tres leyes de la mónada.

Ahora, una forma de modelar mónadas en C # sería construir una interfaz:

interface Monad<M> {
  M<T> pure(T v);
  M<B> bind(M<A> mv, Func<A, M<B>> f);
}

(Nota: para mantener las cosas breves y expresivas, me tomaré algunas libertades con el código a lo largo de esta respuesta).

Ahora podemos implementar mónadas para tipos de datos concretos mediante la implementación de implementaciones concretas de Monad<M> . Por ejemplo, podríamos implementar la siguiente mónada para 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);
  }
}

(A propósito, estoy usando la sintaxis de LINQ para señalar la relación entre la sintaxis de LINQ y las mónadas. Pero tenga en cuenta que podríamos reemplazar la consulta de LINQ con una llamada a SelectMany .)

Ahora, ¿podemos definir una mónada para IObservable ? Parecería que sí:

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);
  }
}

Para estar seguros de que tenemos una mónada, debemos probar las leyes de la mónada. Esto puede ser no trivial (y no estoy lo suficientemente familiarizado con Rx.NET para saber si se pueden probar solo con la especificación), pero es un comienzo prometedor. Para facilitar el resto de esta discusión, asumamos que las leyes de la mónada se mantienen en este caso.

Mónadas libres

No hay singular "mónada libre". Más bien, las mónadas libres son una clase de mónadas que se construyen a partir de funtores. Es decir, dado un functor F , podemos derivar automáticamente una mónada para F (es decir, la mónada libre de F ).

Functors

Al igual que las mónadas, los funtores se pueden definir mediante los siguientes tres elementos:

  • Un tipo de datos, parametrizado sobre una sola variable de tipo sin restricciones.
  • Dos operaciones:

    • pure envuelve un valor puro en el functor. Esto es análogo a pure para una mónada. De hecho, para los funtores que también son una mónada, los dos deben ser idénticos.
    • fmap asigna valores en la entrada a nuevos valores en la salida a través de una función dada. Su firma es:

      F<B> fmap(Func<A, B> f, F<A> fv)
      

Al igual que las mónadas, los funtores están obligados a obedecer las leyes de los funtores.

Similar a las mónadas, podemos modelar los funtores a través de la siguiente interfaz:

interface Functor<F> {
  F<T> pure(T v);
  F<B> fmap(Func<A, B> f, F<A> fv);
}

Ahora, como las mónadas son una subclase de functores, también podríamos refactorizar Monad a bit:

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));
  }
}

Aquí agregué un método adicional, join , y proporcioné implementaciones predeterminadas de join y bind . Tenga en cuenta, sin embargo, que estas son definiciones circulares. Así que tendrías que anular al menos uno o el otro. Además, tenga en cuenta que pure ahora se hereda de Functor .

IObservable y Free Monads

Ahora, como hemos definido una mónada para IObservable y dado que las mónadas son una subclase de functores, se debe poder definir una instancia de functor para IObservable . Aquí hay una definición:

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);
  }
}

Ahora que tenemos un functor definido para IObservable , podemos construir una mónada libre a partir de ese functor. Y así es precisamente cómo IObservable se relaciona con las mónadas libres, es decir, que podemos construir una mónada libre a partir de IObservable .

    
respondido por el Nathan Davis 07.09.2017 - 00:29

Lea otras preguntas en las etiquetas