多范式语言的有用单子

Useful monads for multi-paradigm languages

一旦我理解了 monad 在 c++/python 中的用途,我终于了解了 monad,因为它允许您将 T -> Generic<U> 形式的函数链接在一起。例如,如果您有

readfile = [](string s) -> optional<string> {...};
http_request = [](string s) -> optional<int> {...};
inverse = [](int i) -> optional<int> {...}

然后绑定 >>= :: optional<T> -> (T -> optional<U>) -> optional<U> 具有完全正确的签名以允许您编写这些函数

optional{"myfile"} >>= readfile >>= http_request >>= inverse;
// or in haskell
read_request_inverse = (>>= inverse) . (>>= http_request) . readfile

不用手写短路条件

[]() -> optional<int>
{
    auto message = readfile("myfile");
    if (!message)
        return nullopt
    auto number = http_request(*message)
    if (!number)
        return nullopt
    return inverse(*number)
}

我认为让我感到困惑的是没有区分链式绑定和嵌套绑定(Bartosz Milewski demonstrates with c++ ranges)并分别理解它们

auto triples =
  for_each(ints(1), [](int z) {
    return for_each(ints(1, z), [=](int x) {
      return for_each(ints(x, z), [=](int y) {
        return yield_if(x*x + y*y == z*z, std::make_tuple(x, y, z));
      });
    });
  });

这只是列表理解

triples = [(x, y, z) | z <- [1..]
                     , x <- [1..z]
                     , y <- [x..z]
                     , x^2 + y^2 == z^2]

此外,我相信我被 reader、writer 和 state monads 只是试图逆向工程副作用这一事实绊倒了(我不完全确定不会重新引入不纯 procedures/subroutines) 的问题,因此在多范式语言中没有用处,在这些语言中,您只能拥有真实的(明显受限的)副作用。

所以 monadic optional<T>/result<T,E> 在 c++/python/rust 中似乎非常有用,而 monadic ranges/lists 可能有助于解决编码挑战,但对于现实生活中的问题却不是。

所以我的问题是,是否还有任何其他 monad 示例可以证明 monad 在多范式语言中的有用性?

最能使用 monad 的编程语言当然是函数式编程语言。您在问题中使用了 Haskell,但 monad 在 OCaml 中也很有用。

此编程模式的第一个有用示例在 Nix. Nix is a package manager, that uses scripts written in nix (yes, there's a bit of overlap with terminology) to describe packaging, as well as other things. If you were wondering, yes, nix is a pure, lazy functional programming language. It goes as far as having an entire OS whose configuration is written in nix, called NixOS 中。当您打包应用程序时,monad 模式经常出现,尽管是以一种相当复杂的方式。这个想法是包本身是 monad,有一个仿函数允许你链接包的修改。在 pseudo-code 中,保留您的符号,它可能类似于 package >>= apply_patch >>= change_version >>= add_tests.

另一个示例在 Rust 中。与 Haskell、OCaml 或 nix 相反,Rust 不是一种函数式编程语言(更像是一种系统编程语言),但它具有函数式编程语言的许多方面,以及它们的许多模式,包括 option 一个,例如,在 Rust 中,称为 Option<T>(在 T 上通用)。除了基本的 map 仿函数之外,Rust 还提供了几种方法来处理 Option<T> 对象,因此典型的 Rust 代码可能看起来像(假设 a: Option<T>

a
  .map(|x| ...)
  .filter(|x| ...)
  .and_then(|x| ...)
  .or_default()

然而,Rust 并没有完全以相同的方式处理 monads Haskell,比如说,会处理它们,因为在 Rust 中,monad-compatible(即。 does_something: fn(U) -> Option<T>) 实际上会解包 monad-contained 值,直接对该值进行操作,然后重新包装它们。使用 ? 运算符可以使这变得简单,它只是宏(try! 宏)的语法糖。用法如下,if a: Option<T>.

fn do_something_with_a(a: Option<T>) -> Option<U> {
  let b = do_something_with_a_value(a?);
  Some(b)
}

此处,? 运算符首先 match 遍历 a。如果是None,那么函数马上returns None。否则,它是 Some(x)x 是 returned。也就是说,此 ? 运算符的行为与 >>= 非常相似,只是它不采用闭包,并且 运行 它在 x 上,如果 aSome(x)。相反,它会让当前函数好像是那个闭包,让它直接在 x.

上运行

到目前为止,还不错。但是 Rust 有其他具有这种单子模式的预定义类型,这不在您的原始问题中。例如,Result<T, E>。在 Rust 中,你通常希望避免 throwpanic!raise,或者你想怎样称呼它。这不是异常管理的完成方式。相反,如果 return 类型为 T 的函数可能会抛出类型为 E 的错误,那么它应该改为 return Result<T, E>。它(如您想象的那样)简单地定义为一个枚举,就像 Option<T> 是:

enum Result<T, E> {
  Ok(T),
  Err(E),
}

并且,就像 Option<T> 一样,Rust 提供了一个基本的函子来执行此类型,即 map(即 my_result.map(|x| ...))。然而,就像 Option 一样,它也提供了许多其他方法来处理 Result 对象,但有一种方法特别惯用:? 运算符(是的,又是)!就像选项一样,它的作用如下:

  • 如果对象的形式是Ok(x),那么它的值是x
  • 否则,它的形式是 Err(y),它将 return 来自当前函数 Err(y)

实际上,? 甚至比那更聪明一点,因为您的函数可能会调用多个 return Result<_, _> 的函数,但具有不同的 error-types。例如,如果您尝试读取文件,您可能会遇到 IO 错误。然后你尝试解析一些数据,你可能会得到一个解析器错误。然后你做一些算术,你可能会得到一个算术错误。为了处理这个问题,你的函数应该 return 一个 generic-enough 错误,这样 ? 可以在你可能得到的错误(IO、解析、算术...)和你 return.

React tries to be a comonad

React Suspense is to a Monad as Hooks are to Applicative Notation