Free monad和Reactive Extensions如何关联?


14

我来自C#背景,其中LINQ演变为Rx.NET,但始终对FP感兴趣。在对Monad和F#中的一些辅助项目进行了一些介绍之后,我准备尝试进入下一个级别。

现在,经过来自Scala的人们关于免费monad的几次讨论,以及在Haskell或F#中的多次写作之后,我发现带有用于理解的口译员的语法与IObservable锁链非常相似。

在FRP中,您将根据较小的领域特定块(包括链中的副作用和失败)组成操作定义,并将应用程序建模为一组操作和副作用。在免费monad中,如果我理解正确,您可以通过将操作设为函子,然后使用coyoneda来进行操作。

将针头朝任何一种方法倾斜的两者之间有什么区别?定义服务或程序时的根本区别是什么?


2
这是一种思考问题的有趣方法。FRP可以看作是monad,即使通常不是那样编写的。大多数(尽管不是全部)单子与自由单子同构。正如Cont我所见过的唯一一个单声道建议不能通过免费单声道表示的那样,人们可能会认为FRP可以表示。正如几乎任何东西
Jules

2
根据LINQ和Rx.NET的设计师Erik Meijer的说法,它IObservable是延续monad的一个实例。
约尔格W¯¯米塔格

1
我现在没有时间确定细节,但是我的猜测是RX扩展和免费monad方法都实现了非常相似的目标,但结构可能略有不同。RX Observables本身可能是monad,然后您可以使用observables将一个免费monad计算映射到一个,这大致就是“ free monad”中“ free”的意思。也许关系不是那么直接,您只是在了解如何将它们用于类似目的。
蒂洪·耶尔维斯

Answers:


6

单子

一个单子组成

  • 一个endofunctor。在我们的软件工程世界中,我们可以说这对应于具有单个非限制类型参数的数据类型。在C#中,形式如下:

    class M<T> { ... }
    
  • 在该数据类型上定义了两个操作:

    • return/ pure取一个“纯”值(即一个T值)并将其“包装”到monad中(即它产生一个M<T>值)。由于return是C#中的保留关键字,pure因此从现在开始我将使用它来引用此操作。在C#中,pure将是带有如下签名的方法:

      M<T> pure(T v);
      
    • bind/ flatmap取一个单调值(M<A>)和一个函数ff接受一个纯值,然后返回一个Monadic值(M<B>)。从这些,bind产生新的一元值(M<B>)。 bind具有以下C#签名:

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

此外,要成为一个单子,pure并且bind必须遵守三个单子法。

现在,在C#中建模monad的一种方法是构造一个接口:

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

(注意:为了使内容简洁明了,我将在此答案中放一些代码的自由。)

现在,我们可以通过实现的具体实现来实现用于具体数据类型的monad Monad<M>。例如,我们可以实现以下monad 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语法和monad之间的关系。但是请注意,我们可以用调用代替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,我们可以自动派生一个monad F(即的自由monad 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是函子的子类,我们还可以重构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 和免费的Monad

现在,由于我们已经定义了monad IObservable并且monad是函子的子类,因此,我们必须能够为定义函子实例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,我们可以从该函子构造一个免费的monad。这正是IObservable与自由monad的关系,即我们可以从构造一个自由monad IObservable


深刻理解范畴论!我一直在谈论关于它们是如何创建的,而不是谈论它们是如何建立功能体系结构和建模效果组合时的差异。FreeMonad可用于构建DSL以进行标准化操作,而IObservables则更多地是随时间推移的离散值。
MLProgrammer-CiM

1
@ MLProgrammer-CiM,我将看看是否可以在接下来的几天中对此添加一些见解。
内森·戴维斯

我喜欢自由的单子一个实际的例子
“”“”“”“”“”“” --''''''---------升
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.