Como a mônada livre e as extensões reativas se correlacionam?

14

Eu venho de um background em C #, onde o LINQ evoluiu para o Rx.NET, mas sempre teve algum interesse no FP. Após algumas introduções às mônadas e alguns projetos paralelos em F #, eu estava pronto para tentar avançar para o próximo nível.

Agora, depois de várias palestras sobre a mônada livre de pessoas de Scala e vários artigos escritos em Haskell, ou F #, eu encontrei gramáticas com intérpretes para a compreensão serem bastante semelhantes a IObservablecadeias.

No FRP, você compõe uma definição de operação a partir de blocos menores específicos de domínio, incluindo efeitos colaterais e falhas que permanecem dentro da cadeia, e modela seu aplicativo como um conjunto de operações e efeitos colaterais. Na mônada livre, se eu entendi corretamente, você faz o mesmo fazendo suas operações como functores e levantando-as usando coyoneda.

Quais seriam as diferenças entre os dois que inclinam a agulha em direção a qualquer uma das abordagens? Qual é a diferença fundamental ao definir seu serviço ou programa?

MLProgrammer-CiM
fonte
2
Aqui está uma maneira interessante de pensar sobre o problema ... O FRP pode ser visto como uma mônada, mesmo que não seja geralmente formulada dessa maneira . A maioria (embora não todas) das mônadas é isomórfica à mônada livre . Como Conté a única mônada que vi sugerida que não pode ser expressa através da mônada livre, é provável que se possa supor que o FRP possa ser. Como pode quase qualquer outra coisa .
Jules
2
Segundo Erik Meijer, o designer do LINQ e do Rx.NET, IObservableé uma instância da mônada de continuação.
Jörg W Mittag
1
Não tenho tempo para resolver os detalhes no momento, mas meu palpite é que as extensões RX e a abordagem de mônada livre alcançam objetivos muito semelhantes, mas podem ter estruturas ligeiramente diferentes. É possível que o RX Observables seja uma mônada e, em seguida, você possa mapear um cálculo de mônada livre para outro usando observáveis ​​- é muito mais ou menos o que significa "livre" em "mônada livre". Ou talvez o relacionamento não seja tão direto e você esteja apenas começando a entender como eles são usados ​​para fins semelhantes.
Tikhon Jelvis 29/07

Respostas:

6

Mônadas

Uma mônada consiste em

  • Um endofuncor . Em nosso mundo de engenharia de software, podemos dizer que isso corresponde a um tipo de dados com um único parâmetro de tipo irrestrito. Em C #, isso seria algo da forma:

    class M<T> { ... }
    
  • Duas operações definidas sobre esse tipo de dados:

    • return/ purepega um valor "puro" (ou seja, um Tvalor) e o "envolve" na mônada (ou seja, produz um M<T>valor). Como returné uma palavra-chave reservada em C #, usarei purepara me referir a essa operação a partir de agora. Em C #, pureseria um método com uma assinatura como:

      M<T> pure(T v);
      
    • bind/ flatmappega um valor monádico ( M<A>) e uma função f. fpega um valor puro e retorna um valor monádico ( M<B>). A partir disso, bindproduz um novo valor monádico ( M<B>). bindtem a seguinte assinatura C #:

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

Além disso, para ser uma mônada, puree bindsão obrigados a obedecer às três leis da mônada.

Agora, uma maneira de modelar mônadas em C # seria construir uma interface:

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

(Observação: para manter as coisas breves e expressivas, usarei algumas liberdades com o código ao longo desta resposta.)

Agora podemos implementar mônadas para tipos de dados concretos implementando implementações concretas de Monad<M>. Por exemplo, podemos implementar a seguinte 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);
  }
}

(Estou usando intencionalmente a sintaxe LINQ para chamar o relacionamento entre a sintaxe LINQ e as mônadas. Mas observe que poderíamos substituir a consulta LINQ por uma chamada para SelectMany.)

Agora, podemos definir uma mônada para IObservable? Parece que sim:

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 ter certeza de que temos uma mônada, precisamos provar as leis da mônada. Isso pode não ser trivial (e eu não estou familiarizado o suficiente com o Rx.NET para saber se eles podem ser provados apenas a partir da especificação), mas é um começo promissor. Para facilitar o restante desta discussão, vamos apenas assumir que as leis de mônada são válidas nesse caso.

Mônadas grátis

Não existe uma "mônada livre" singular. Em vez disso, mônadas livres são uma classe de mônadas que são construídas a partir de functores. Ou seja, dado um functor F, podemos derivar automaticamente uma mônada para F(isto é, a mônada livre de F).

Functors

Como mônadas, os functores podem ser definidos pelos três itens a seguir:

  • Um tipo de dados, parametrizado em uma única variável de tipo irrestrita.
  • Duas operações:

    • pureagrupa um valor puro no functor. Isso é análogo a pureuma mônada. De fato, para os functores que também são mônadas, os dois devem ser idênticos.
    • fmapmapeia valores na entrada para novos valores na saída através de uma determinada função. Sua assinatura é:

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

Como mônadas, os functores são obrigados a obedecer às leis dos functores.

Semelhante às mônadas, podemos modelar functors através da seguinte interface:

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

Agora, como as mônadas são uma subclasse de functores, também podemos refatorar Monadum pouco:

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

Aqui eu adicionei um método adicional,, joine forneci implementações padrão de ambos joine bind. Observe, no entanto, que essas são definições circulares. Então você teria que substituir pelo menos um ou outro. Além disso, observe que pureagora é herdado de Functor.

IObservable e Mônadas Grátis

Agora, como definimos uma mônada para IObservablee como as mônadas são uma subclasse de functores, segue-se que devemos ser capazes de definir uma instância de functor para IObservable. Aqui está uma definição:

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

Agora que temos um functor definido IObservable, podemos construir uma mônada livre a partir desse functor. E é exatamente assim que IObservablese relaciona com as mônadas livres - a partir da qual podemos construir uma mônada livre IObservable.

Nathan Davis
fonte
Compreensão perspicaz da teoria das categorias! Eu estava procurando algo que não falasse sobre como eles são criados, mais sobre as diferenças ao criar uma arquitetura funcional e modelar a composição de efeitos com qualquer um deles. O FreeMonad pode ser usado para criar DSLs para operações reificadas, enquanto IObservables são mais sobre valores discretos ao longo do tempo.
MLProgrammer-CiM
1
@ MLProgrammer-CiM, vou ver se posso acrescentar algumas idéias sobre isso nos próximos dias.
Nathan Davis
Eu adoraria um exemplo prático de mônadas livres
l