In che modo la monade libera e le estensioni reattive sono correlate?


14

Vengo da uno sfondo C #, in cui LINQ si è evoluto in Rx.NET, ma ha sempre avuto un certo interesse per FP. Dopo alcune introduzioni alle monadi e alcuni progetti secondari in F #, ero pronto a provare a passare al livello successivo.

Ora, dopo diversi discorsi sulla monade libera da parte di persone della Scala, e molteplici scritture in Haskell, o F #, ho scoperto che le grammatiche con gli interpreti per la comprensione sono abbastanza simili alle IObservablecatene.

In FRP si compone una definizione di operazione da blocchi specifici di dominio più piccoli inclusi effetti collaterali e guasti che rimangono all'interno della catena e si modella la propria applicazione come un insieme di operazioni ed effetti collaterali. In monade libera, se ho capito bene, fai lo stesso facendo le tue operazioni come funzioni e sollevandole usando la coyoneda.

Quali sarebbero le differenze tra entrambi che inclinano l'ago verso uno qualsiasi degli approcci? Qual è la differenza fondamentale nella definizione del tuo servizio o programma?


2
Ecco un modo interessante di pensare al problema ... FRP può essere visto come una monade, anche se di solito non è formulato in questo modo . La maggior parte (anche se non tutte) le monadi sono isomorfe alla monade libera . Come Contè l'unica monade che ho visto suggerire che non può essere espressa tramite la monade libera, si può presumere che FRP possa esserlo. Come quasi ogni altra cosa .
Jules,

2
Secondo Erik Meijer, il designer di LINQ e Rx.NET, IObservableè un'istanza della monade della continuazione.
Jörg W Mittag,

1
Non ho il tempo di elaborare i dettagli in questo momento, ma la mia ipotesi è che entrambe le estensioni RX e l'approccio della monade libera raggiungano obiettivi molto simili ma potrebbero avere strutture leggermente diverse. È possibile che RX Observables siano esse stesse monadi e quindi è possibile mappare un calcolo di monade libero su uno che utilizza osservabili - è molto più o meno ciò che significa "libero" in "monade libera". O forse la relazione non è così diretta e stai solo scoprendo come vengono utilizzati per scopi simili.
Tikhon Jelvis,

Risposte:


6

monadi

Una monade è composta da

  • Un endofunctor . Nel nostro mondo dell'ingegneria del software, possiamo dire che questo corrisponde a un tipo di dati con un singolo parametro di tipo senza restrizioni. In C #, questo sarebbe qualcosa della forma:

    class M<T> { ... }
    
  • Due operazioni definite su quel tipo di dati:

    • return/ pureprende un valore "puro" (cioè un Tvalore) e lo "avvolge" nella monade (cioè produce un M<T>valore). Poiché returnè una parola chiave riservata in C #, userò pureper fare riferimento a questa operazione da ora in poi. In C #, puresarebbe un metodo con una firma come:

      M<T> pure(T v);
      
    • bind/ flatmapaccetta un valore monadico ( M<A>) e una funzione f. faccetta un valore puro e restituisce un valore monadico ( M<B>). Da questi, bindproduce un nuovo valore monadico ( M<B>). bindha la seguente firma C #:

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

Inoltre, per essere una monade, puree bindsono tenuti ad obbedire alle tre leggi della monade.

Ora, un modo per modellare le monadi in C # sarebbe costruire un'interfaccia:

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

(Nota: al fine di mantenere le cose brevi ed espressive, mi prenderò alcune libertà con il codice in tutta questa risposta.)

Ora possiamo implementare monadi per tipi di dati concreti implementando implementazioni concrete di Monad<M>. Ad esempio, potremmo implementare la seguente monade per 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);
  }
}

(Sto usando intenzionalmente la sintassi LINQ per richiamare la relazione tra sintassi LINQ e monadi. Ma nota che potremmo sostituire la query LINQ con una chiamata a SelectMany.)

Ora, possiamo definire una monade per IObservable? Sembrerebbe di si:

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

Per essere sicuri di avere una monade, dobbiamo dimostrare le leggi della monade. Questo può essere non banale (e non ho abbastanza familiarità con Rx.NET per sapere se possono essere provati anche solo dalle specifiche), ma è un inizio promettente. Per facilitare il resto di questa discussione, supponiamo che le leggi sulla monade siano valide in questo caso.

Monadi libere

Non esiste una "monade libera" singolare. Piuttosto, le monadi libere sono una classe di monadi costruite da funzioni. Cioè, dato un funzione F, possiamo automaticamente derivare una monade per F(cioè la monade libera di F).

funtori

Come le monadi, i funzione possono essere definiti dai seguenti tre elementi:

  • Un tipo di dati, parametrizzato su una singola variabile di tipo senza restrizioni.
  • Due operazioni:

    • pureavvolge un valore puro nel functor. Questo è analogo a pureper una monade. In effetti, per i funzioni che sono anche monadi, i due dovrebbero essere identici.
    • fmapassocia i valori nell'input a nuovi valori nell'output tramite una determinata funzione. La sua firma è:

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

Come le monadi, i funzioni sono tenuti a obbedire alle leggi dei funzioni.

Simile alle monadi, possiamo modellare i funzione tramite la seguente interfaccia:

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

Ora, poiché le monadi sono una sottoclasse di funzioni, potremmo anche rifattorizzare Monadun po ':

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

Qui ho aggiunto un metodo aggiuntivo joine fornito implementazioni predefinite di entrambi joine bind. Si noti, tuttavia, che si tratta di definizioni circolari. Quindi dovresti scavalcare almeno l'uno o l'altro. Inoltre, si noti che pureora è ereditato da Functor.

IObservable e Monadi libere

Ora, poiché abbiamo definito una monade per IObservablee poiché le monadi sono una sottoclasse di funzioni, ne consegue che dobbiamo essere in grado di definire un'istanza di funzione per IObservable. Ecco una definizione:

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

Ora che abbiamo definito un funzione IObservable, possiamo costruire una monade libera da quel funzione. Ed è proprio così che IObservableriguarda le monadi libere - vale a dire, da cui possiamo costruire una monade libera IObservable.


Comprensione approfondita della teoria delle categorie! Stavo cercando qualcosa che non parlava di come sono stati creati, più delle differenze quando si costruisce un'architettura funzionale e si modella la composizione di effetti con uno di essi. FreeMonad può essere utilizzato per creare DSL per operazioni reificate, mentre IObservables riguarda più i valori discreti nel tempo.
MLProgrammer-CiM

1
@ MLProgrammer-CiM, vedrò se posso aggiungere alcuni approfondimenti su questo nei prossimi due giorni.
Nathan Davis,

Mi piacerebbe un esempio pratico di monadi libere
l --''''''--------- '' '' '' '' '' ''
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.