Observable真的是单子吗?是否遵守Monad法律(https://wiki.haskell.org/Monad_laws)?在我看来,它并不像它那样。但是也许我的理解是错误的,有人可以阐明这个问题。我目前的推理是(我使用 :: 表示“是同类”):
1)左身份: 返回>> = f≡f a
var func = x => Rx.Observable.of(10)
var a = Rx.Observable.of(1).flatMap(func) :: Observable
var b = func(1) :: ScalarObservable
哈斯克尔:
func = (\_ -> putStrLn "B")
do { putStrLn "hello"; return "A" } >>= func :: IO ()
func "A" :: IO ()
因此,左身份对于Observable不适用。可观察的显然不是ScalarObservable。在Haskell中,类型相同- IO()。
2)正确的身份: m >> =返回≡m
var x = Rx.Observable.of(1);
x.flatMap(x => Observable.of(x)) :: Observable
x :: ScalarObservable
哈斯克尔:
Just 2 >>= return :: Num b => Maybe b
Just 2 :: Num a => Maybe a
与左身份相同的情况。可观察!== ScalarObservable。在Haskell中,类型保持不变,但可能是其中包含Num的Maybe。
3)关联性
(m >> = f)>> = g≡m >> =(\ x-> f x >> = g)
var x = Rx.Observable.of(10)
var func1 = (x) => Rx.Observable.of(x + 1)
var func2 = (x) => Rx.Observable.of(x + 2)
x.flatMap(func1).flatMap(func2) :: Observable
x.flatMap(e => func1(e).flatMap(func2)) :: Observable
哈斯克尔:
add2 x = Just(x + 2)
add1 x = Just(x + 1)
Just 2 >>= add1 >>= add2 :: Num b => Maybe b
Just 2 >>= (\x -> add1(x) >>= add2) :: Num b => Maybe b
这是唯一似乎适用于Observable的法律。 但是我不知道,也许这不应该像我那样推理。你觉得呢?
答案 0 :(得分:1)
tldr; 是。
JavaScript是一种带有鸭子输入的动态语言,因此在运行时,Observable
类的实例与ScalarObservable
的实例等效。 RxJS本身是用TypeScript编写的,这些不规则性并没有在类型中浮出水面,而正如@Bergi在评论中所写的那样,它们是一种优化。另一方面,您是完全正确的:在名义类型系统中,类型不匹配可能是一个实际问题,甚至是编译时错误。
现在,回答问题本身-请查看具有绑定到RxJS的Purescript library:
foreign import data Observable :: Type -> Type
instance monoidObservable :: Monoid (Observable a) where
mempty = _empty
instance functorObservable :: Functor Observable where
map = _map
instance applyObservable :: Apply Observable where
apply = combineLatest id
instance applicativeObservable :: Applicative Observable where
pure = just
instance bindObservable :: Bind Observable where
bind = mergeMap
instance monadObservable :: Monad Observable
-- | NOTE: The semigroup instance uses `merge` NOT `concat`.
instance semigroupObservable :: Semigroup (Observable a) where
append = merge
instance altObservable :: Alt Observable where
alt = merge
instance plusObservable :: Plus Observable where
empty = _empty
instance alternativeObservable :: Alternative Observable
instance monadZeroObservable :: MonadZero Observable
instance monadPlusObservable :: MonadPlus Observable
instance monadErrorObservable :: MonadError Error Observable where
catchError = catch
instance monadThrowObservable :: MonadThrow Error Observable where
throwError = throw
假定Purescript类型是正确的:除了是常规Monad
之外,Observable
符合MonadPlus
和MonadError
类。 MonadPlus
允许组合计算,而MonadError
允许中断或跳过部分计算(对于Observable
,我们也可以轻松地重试计算)。
Observable
不仅是monad,而且是功能非常强大的 ,甚至是主要 stream $ 中使用的功能最强大的monad。
我没有任何正式的证明,但是可以简短地描述如何使用Observable建模或替换https://wiki.haskell.org/All_About_Monads中描述的单子。
也许 可能无法返回结果的计算
无结果可以表示为常规JS undefined
或EMPTY
流。
错误 可能失败或引发异常的计算
您可以抛出常规的JS错误,或者从monadic绑定中返回更多惯用的throwError
。可以捕获错误,然后将其处理或用于重试计算。引发错误会立即停止正在进行的计算。
列表 可以返回多个可能结果的不确定计算
List有点像Observable的弟弟,完全没有时间维度。可以通过列表上的操作表示的任何内容都可以精确地映射到可观察对象上的操作。您可以通过Observable.from
轻松提出一个列表,并通过.toList()
降级为可观察的列表。作为本地的,列表性能将比可观察的性能好得多。但是请记住,列表渴望且可观察的是懒惰的,因此在某些情况下,可观察的结果可能胜过列表。
IO 执行I / O的计算
任何IO操作(网络,磁盘等)都可以轻松包装/提升到可观察的世界。
状态 保持状态的计算
BehaviorSubject
阅读器 从共享环境中读取的计算
从消费者的角度来看, Observable 实例的来源根本不重要。例如:如果您将配置声明为可观察到的,则可以轻松更改提供值的确切环境。
编写器 除了计算值外还写入数据的计算
最简单的选择是返回两个流,一个带值,另一个带日志/辅助数据。
继续 可以中断并重新启动的计算
要中断计算,您可能会引发错误,请使用运算符,例如.switchMap
,.takeUntil
,明确取消订阅或.mergeMap
到EMPTY
。可以使用某种形式的缓存从任意步骤重新启动确定性计算,这是很简单的:将计算拆分为较小的可观察对象,并在计算后缓存其结果;重新启动后,仅当缓存为空时才运行计算-否则使用缓存的值。
如果您决定使用可观察性来表示您的计算结构-您不仅可以建模/替换实际中使用的最常见单子,而且您的计算会自动做出反应。此外,如果您仅遵循可观察的方法,那么计算将是同质的,这意味着很少或不需要Monad变压器以及它们带来的意外复杂性。
我的工作假设是,可观察类型为表达异步计算的结构提供了一些局部(甚至全局)最大值。例如:Observable提供的不是一个,不是两个,而是 3!个单子绑定,它们具有不同的语义:mergeMap
,switchMap
,exhaustMap
(如果您想知道,{ {1}}实际上是concatMap
的特例。这个事实本身就表明可观察是一个非常有趣的数学结构。
奖金
可观察到的是流,并且流(通常)是[commonads](https://bartoszmilewski.com/2017/01/02/comonads/)。这是否意味着可观察到的不仅是单子,而且是共性?
@ rix0rrr一段时间以来,Rx都有一个ManySelect运算符。 Rx既是monad又是comonad。 144个字符太短,无法解释。抱歉;-)