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

14

Vengo de un fondo de C #, donde LINQ evolucionó a Rx.NET, pero siempre tenía algún interés en 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 por parte de personas de Scala, y múltiples escritos en Haskell, o F #, he encontrado gramáticas con intérpretes para que la comprensión sea bastante similar a las IObservablecadenas.

En FRP, compones una definición de operación a partir de fragmentos específicos de dominios más pequeños que incluyen efectos secundarios y fallas que permanecen dentro de la cadena, y modelas tu aplicación como un conjunto de operaciones y efectos secundarios. En Mónada libre, si entendí correctamente, usted hace lo mismo haciendo sus operaciones como functors y levantándolos con 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?

MLProgrammer-CiM
fuente
2
Aquí hay una forma interesante de pensar sobre el problema ... FRP se puede ver como una mónada, incluso si no se formula de esa manera . La mayoría (aunque no todas) las mónadas son isomorfas a la mónada libre . Como Contes la única mónada que he visto que sugiere que no puede expresarse a través de la mónada libre, uno probablemente puede suponer que FRP sí puede serlo. Como casi cualquier otra cosa .
Jules
2
Según Erik Meijer, el diseñador de LINQ y Rx.NET, IObservablees una instancia de la mónada de continuación.
Jörg W Mittag
1
No tengo tiempo para resolver los detalles en este momento, pero supongo que tanto las extensiones RX como el enfoque de mónada gratis logran objetivos muy similares, pero pueden tener estructuras ligeramente diferentes. Es posible que los Observables RX sean mónadas en sí mismas y luego pueda asignar un cálculo de mónada libre a uno usando observables, eso es más o menos lo que significa "libre" en "mónada libre". O tal vez la relación no es tan directa y solo estás aprendiendo cómo se usan para fines similares.
Tikhon Jelvis

Respuestas:

6

Mónadas

Una mónada consiste en

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

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

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

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

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

Además, para ser una mónada, purey binddeben obedecer 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 implementando 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);
  }
}

(Estoy usando la sintaxis LINQ a propósito para llamar la relación entre la sintaxis LINQ y las mónadas. Pero tenga en cuenta que podríamos reemplazar la consulta 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, necesitamos probar las leyes de la mónada. Esto puede no ser trivial (y no estoy lo suficientemente familiarizado con Rx.NET para saber si incluso se puede probar solo desde la especificación), pero es un comienzo prometedor. Para facilitar el resto de esta discusión, supongamos que las leyes de mónada se mantienen en este caso.

Mónadas Gratis

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

Functores

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

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

    • pureenvuelve un valor puro en el functor. Esto es análogo a purepara una mónada. De hecho, para los functors que también son mónadas, los dos deberían ser idénticos.
    • fmapasigna 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 functores deben obedecer las leyes de los functores.

De manera similar a las mónadas, podemos modelar functores 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, dado que las mónadas son una subclase de functores, también podríamos refactorizar Monadun poco:

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í he agregado un método adicional join, y proporcioné implementaciones predeterminadas de ambos joiny bind. Tenga en cuenta, sin embargo, que estas son definiciones circulares. Por lo tanto, tendría que anular al menos uno u otro. Además, tenga en cuenta que pureahora se hereda de Functor.

IObservable y mónadas gratis

Ahora, dado que hemos definido una mónada para IObservabley dado que las mónadas son una subclase de functores, se deduce que debemos ser capaces de 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 IObservable, podemos construir una mónada libre a partir de ese functor. Y así es precisamente cómo se IObservablerelaciona con las mónadas libres, es decir, a partir de lo cual podemos construir una mónada libre IObservable.

Nathan Davis
fuente
¡Comprensión perspicaz de la teoría de categorías! Estaba buscando algo que no hablara sobre cómo se crean, más sobre las diferencias al construir una arquitectura funcional y la composición de efectos de modelado con cualquiera de ellos. FreeMonad se puede usar para construir DSL para operaciones reificadas, mientras que IObservables se trata más de valores discretos a lo largo del tiempo.
MLProgrammer-CiM
1
@ MLProgrammer-CiM, veré si puedo agregar algunas ideas al respecto en los próximos días.
Nathan Davis
Me encantaría un ejemplo práctico de mónadas gratis
l --''''''--------- '' '' '' '' '' ''