如何实现自定义 Monad 实例,尤其是 FSharpPlus 中的 IObservable?
How to implement custom Monad instance, especially for IObservable in FSharpPlus?
FSharpPlus provided monad CE 和几个 monad 转换器,我想将 ReaderT<'a, IObservable<'b>>
与 FSharpPlus 的 monad
CE 一起使用,这需要定义 IObservable
的 monad 实例。
所需代码的示例是
let test (x: IObservable<int>) =
monad {
let! a = x
let! b = x
return a + b
}
预计将翻译成
let test (v: IObservable<int>) =
x.SelectMany(fun n -> Observable.Return(n + 1))
但是 monad CE 不支持开箱即用的 IObservable
,并且在上面的 test
功能的同一模块中添加如下扩展不起作用。
type IObservable<'a> with
static member Return (x: 'T) : IObservable<'T> =
Observable.Return(x)
static member (>>=) (x: IObservable<'T>, f: 'T->IObservable<'U>) : IObservable<'U> =
x.SelectMany(f)
如何为 IObservable
类型定义 monad 实例?
更新
从
更新用例
let test (x: IObservable<int>) =
monad {
let! n = x
return n + 1
}
到当前一个以防止与 Functor 的无意关系。
更新
正如@Gus 在回答中提到的,直接为 IObservable 添加 monad 实例可能并不容易。
经过一些搜索,由于 IObservable
的 Functor 实例有效(支持 map
和 |>>
),似乎免费的 monad 可能是一种解决方案。
以下代码似乎有效:
open System
open System.Reactive.Linq
open FSharpPlus
open FSharpPlus.Data
let rec interpret (p: Free<IObservable<'a>, 'a>) : IObservable<'a> =
match Free.run p with
| Choice1Of2 x -> Observable.Return(x)
| Choice2Of2 p' -> p'.SelectMany(interpret)
let test =
monad {
let! a = Free.liftF (Observable.Range(0, 4).Select(fun n -> n * 10))
let! b = Free.liftF (Observable.Range(0, 10))
return a + b
}
|> interpret
因此,我们需要做的是在monad CE for IObservable
中的所有bind 之前添加Free.liftF
,并在最后添加一个interpret
辅助函数。
interpret
实现可能不是堆栈安全的(或者可能是 IObservable.SelectMany 实现?)。
尽管对于简单的情况,这编译并且看起来 运行 正确,但我想知道这种免费 monad 的用法是否正确?
在撰写此答案时,无法使扩展方法对特征约束可见,尽管 F# 编译器中有 longstanding RFC 和 PR 来实现它。
因此,目前为了将类型添加到抽象(如 Monad),您需要编辑类型的源以添加所需的方法或抽象源。
前者基本不可能 IObservable
我的意思是你可以尝试向他们提交 PR,但后一种选择在短期内更可行:提交 PR 或至少在 F# 中打开一个问题+ 将 IObservable
直接添加为 Monad。
FSharpPlus provided monad CE 和几个 monad 转换器,我想将 ReaderT<'a, IObservable<'b>>
与 FSharpPlus 的 monad
CE 一起使用,这需要定义 IObservable
的 monad 实例。
所需代码的示例是
let test (x: IObservable<int>) =
monad {
let! a = x
let! b = x
return a + b
}
预计将翻译成
let test (v: IObservable<int>) =
x.SelectMany(fun n -> Observable.Return(n + 1))
但是 monad CE 不支持开箱即用的 IObservable
,并且在上面的 test
功能的同一模块中添加如下扩展不起作用。
type IObservable<'a> with
static member Return (x: 'T) : IObservable<'T> =
Observable.Return(x)
static member (>>=) (x: IObservable<'T>, f: 'T->IObservable<'U>) : IObservable<'U> =
x.SelectMany(f)
如何为 IObservable
类型定义 monad 实例?
更新
从
更新用例let test (x: IObservable<int>) =
monad {
let! n = x
return n + 1
}
到当前一个以防止与 Functor 的无意关系。
更新
正如@Gus 在回答中提到的,直接为 IObservable 添加 monad 实例可能并不容易。
经过一些搜索,由于 IObservable
的 Functor 实例有效(支持 map
和 |>>
),似乎免费的 monad 可能是一种解决方案。
以下代码似乎有效:
open System
open System.Reactive.Linq
open FSharpPlus
open FSharpPlus.Data
let rec interpret (p: Free<IObservable<'a>, 'a>) : IObservable<'a> =
match Free.run p with
| Choice1Of2 x -> Observable.Return(x)
| Choice2Of2 p' -> p'.SelectMany(interpret)
let test =
monad {
let! a = Free.liftF (Observable.Range(0, 4).Select(fun n -> n * 10))
let! b = Free.liftF (Observable.Range(0, 10))
return a + b
}
|> interpret
因此,我们需要做的是在monad CE for IObservable
中的所有bind 之前添加Free.liftF
,并在最后添加一个interpret
辅助函数。
interpret
实现可能不是堆栈安全的(或者可能是 IObservable.SelectMany 实现?)。
尽管对于简单的情况,这编译并且看起来 运行 正确,但我想知道这种免费 monad 的用法是否正确?
在撰写此答案时,无法使扩展方法对特征约束可见,尽管 F# 编译器中有 longstanding RFC 和 PR 来实现它。
因此,目前为了将类型添加到抽象(如 Monad),您需要编辑类型的源以添加所需的方法或抽象源。
前者基本不可能 IObservable
我的意思是你可以尝试向他们提交 PR,但后一种选择在短期内更可行:提交 PR 或至少在 F# 中打开一个问题+ 将 IObservable
直接添加为 Monad。