RxJS.Observable是monad吗?

Mat*_*Wit 15 javascript haskell observable rxjs

Observable真的是monad吗?是否遵守Monad法律(https://wiki.haskell.org/Monad_laws)?在我看来似乎不喜欢它.但也许我的理解是错误的,有人可以对这个问题有所了解.我目前的推理是(我正在使用::来表示"善良"):

1)左侧身份: 返回>> =f≡fa

var func = x => Rx.Observable.of(10)

var a = Rx.Observable.of(1).flatMap(func) :: Observable
var b = func(1)                           :: ScalarObservable
Run Code Online (Sandbox Code Playgroud)

HASKELL:

func = (\_ -> putStrLn "B")

do { putStrLn "hello"; return "A" } >>= func   :: IO ()

func "A"                                       :: IO ()
Run Code Online (Sandbox Code Playgroud)

所以左侧身份不适用于Observable.可观察到的不是ScalarObservable.在Haskell中,类型是相同的 - IO().

2)右同一性: m >> =返回≡m

var x = Rx.Observable.of(1);

x.flatMap(x => Observable.of(x)) :: Observable
x                                :: ScalarObservable
Run Code Online (Sandbox Code Playgroud)

HASKELL:

Just 2 >>= return  :: Num b => Maybe b
Just 2             :: Num a => Maybe a
Run Code Online (Sandbox Code Playgroud)

与左侧身份相同的情况.可观察!== ScalarObservable.而在Haskell中,类型保持不变,而它是一个带有Num的Maybe.

3)相关性

(m >> = f)>> =g≡m>> =(\ x - > fx >> = 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
Run Code Online (Sandbox Code Playgroud)

HASKELL:

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
Run Code Online (Sandbox Code Playgroud)

这是Observable似乎唯一的法律.但我不知道,也许这不应该像我那样推理.你怎么看?

art*_*iak 15

tldr; 是的。


JavaScript 是一种带有鸭子类型的动态语言,因此在运行时,Observable类的实例等效ScalarObservable. RxJS 本身是用 TypeScript 编写的,这些不规则性不会在类型中出现,它们 - 正如@Bergi 在评论中所写的 - 是一种优化。另一方面,您是完全正确的:在名义类型系统中,类型不匹配可能是一个真正的问题,甚至是编译时错误。


现在,回答这个问题本身——请看一个绑定到 RxJS的Purescript 库

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
Run Code Online (Sandbox Code Playgroud)

假设 Purescript 类型是正确的:除了是常规的MonadObservable符合MonadPlusMonadError类。MonadPlus允许组合计算,同时MonadError允许中断或跳过计算的某些部分(如果Observable我们也可以轻松地重试计算)。 Observable不仅是一个monad,而且是一个非常强大的monad——甚至可能主流$中使用的最强大的monad 。

我没有任何正式的证明,但可以简短地描述如何使用 Observable 来建模或替换https://wiki.haskell.org/All_About_Monads 中描述的 monad 。

也许 可能不返回结果的计算

非结果可以表示为常规 JSundefinedEMPTY流。

可能失败或抛出异常的错误计算

您可以抛出常规 JS 错误或throwError从 monadic bind返回更多惯用语。错误可以被捕获,然后被处理或用于重试计算。抛出错误会立即停止正在进行的计算。

列出 可以返回多个可能结果的非确定性计算

List 有点像 Observable 的弟弟,完全没有时间维度。任何可以通过列表操作表达的东西都可以精确地映射到可观察对象上的操作。您可以通过 轻松提升列表Observable.from并使用 降级到可观察.toList()。作为原生,列表的性能将比可观察的好得多。但请记住,列表是急切的,可观察是惰性的,因此在某些情况下,可观察可能会胜过列表。

执行 I/O 的IO计算

任何 IO 操作(网络、磁盘等)都可以轻松包装/提升到可观察世界。

国家 计算中它保持状态

行为主体

从共享环境中读取的读取计算

从消费者的角度来看,Observable的实例来自哪里根本无关紧要。例如:如果您将配置声明为 observable,您可以轻松更改提供值的确切环境。

作家 计算中,其写在除数据到计算值

最简单的选择是返回两个流,一个带有值,另一个带有日志/辅助数据。

可以中断和重新启动的连续计算

要中断计算,您可以抛出错误,请使用运算符,例如.switchMap, .takeUntil,明确取消订阅或.mergeMapto EMPTY。访问某种形式的缓存以从任意步骤重新启动确定性计算是非常简单的:只需将您的计算拆分为较小的 observables 并在计算后缓存它们的结果;仅当缓存为空时重新启动运行计算 - 否则使用缓存值。


如果您决定使用 observable 来表示您的计算结构 - 您不仅可以建模/替换实践中使用的最常见的 monad,而且您的计算在风格上会自动响应。此外,如果你坚持只使用可观察的,你的计算将是同质的,这意味着很少或不需要 monad 转换器和它们引入的意外复杂性。我的工作假设是 observable 类型为表达异步计算的结构提供了一些局部(甚至全局)最大值。例如: Observable 提供的不是一个,不是两个,而是三个!monadic 绑定了不同的语义:mergeMap, switchMap, exhaustMap(如果你想知道:concatMap实际上是mergeMap)。这个事实本身就表明 observable 是一个非常有趣的数学结构。


奖金

Observable 被认为是一个流,而流(通常)是 [commonads]​​(https://bartoszmilewski.com/2017/01/02/comonads/)。这是否意味着 observable 不仅是一个单子而且也是一个共子?

Erik Meijer 的推文

@rix0rrr 有一段时间 Rx 有一个 ManySelect 操作符。Rx 既是单子又是共子。144 个字符太短,无法解释。对不起 ;-)