【问题标题】:Useful monads for multi-paradigm languages多范式语言的有用单子
【发布时间】:2022-06-18 16:20:09
【问题描述】:

当我通过允许您将 T -> Generic<U> 形式的函数链接在一起来了解它们在 c++/python 中的用途时,我终于开始了解 monad。例如,如果您有

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

然后 bind &gt;&gt;= :: optional&lt;T&gt; -&gt; (T -&gt; optional&lt;U&gt;) -&gt; optional&lt;U&gt; 具有完全正确的签名以允许您编写这些函数

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 monad 只是试图对副作用进行逆向工程(我不完全确定这不会重新引入不纯过程/子例程的问题),因此在多范式语言中没有用处,在这种语言中,您只能有真正的(明显受限的)副作用。

所以 monadic optional&lt;T&gt;/result&lt;T,E&gt; 在 c++/python/rust 中似乎非常有用,而 monadic 范围/列表可能有助于解决编码挑战,但 并不是真正解决现实生活中的问题(编辑:我已经开始大量使用flatmap/views::for_each 来简化“业务逻辑”。

所以我的问题是,还有其他单子示例可以证明单子在多范式语言中的有用性吗?

【问题讨论】:

    标签: monads


    【解决方案1】:

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

    这种编程模式有用的第一个例子是Nix。 Nix 是一个包管理器,它使用用 nix 编写的脚本(是的,与术语有一些重叠)来描述打包以及其他内容。如果您想知道,是的,nix 是一种纯粹的、惰性的函数式编程语言。它甚至拥有一个完整的操作系统,其配置是用 nix 编写的,称为NixOS。当您打包应用程序时,monad 模式经常出现,尽管方式非常复杂。这个想法是,包本身就是 monad,有一个函子,允许您链接包的修改。在伪代码中,保留您的符号,它可能类似于 package &gt;&gt;= apply_patch &gt;&gt;= change_version &gt;&gt;= add_tests

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

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

    然而,Rust 处理 monad 的方式与 Haskell 处理它们的方式不同,因为在 Rust 中,与 monad 兼容的函数(即does_something: fn(U) -&gt; Option&lt;T&gt;)实际上会解压monad 包含的值,直接对该值进行操作,然后重新包装它们。这通过? 运算符变得简单,它只是宏的语法糖(try! 之一)。用法如下,如果a: Option&lt;T&gt;

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

    这里,? 运算符首先通过matching 超过a。如果是None,则函数立即返回None。否则,它是一个Some(x),并返回x。也就是说,这个? 操作符与&gt;&gt;= 非常相似,只是它不使用闭包,并且如果aSome(x),则在x 上运行它。相反,它会让当前函数就像那个闭包一样,让它直接在x 上运行。

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

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

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

    • 如果对象的形式为Ok(x),则其值为x
    • 否则,它的形式为Err(y),它将从当前函数返回Err(y)

    实际上,? 甚至比这更聪明一点,因为您的函数可能会调用多个返回 Result&lt;_, _&gt; 的函数,但具有不同的错误类型。例如,如果您尝试从文件中读取,您可能会收到 IO 错误。然后你尝试解析一些数据,你可能会得到一个解析器错误。然后你做一些算术,你可能会得到一个算术错误。为了解决这个问题,你的函数应该返回一个足够通用的错误,以便? 可以在你可能得到的错误(IO、解析、算术......)和你返回的错误之间执行转换。

    【讨论】:

    • 谢谢我不知道?。它允许您定义 (&gt;&gt;= http_request) 而不是 http_request 本身。比较利弊会很有趣。但是,我的问题确实是它们是否有任何不是 optional/result 的有用单子(它们基本上是相同的单子)
    • 我没有意识到 nix 正在使用 monads。所以覆盖模式和定点评估是一元的。如果您按照这篇博文blog.tpleyer.de/posts/… 的条款制定package &gt;&gt;= apply_patch &gt;&gt;= change_version &gt;&gt;= add_tests,将会很感兴趣
    • @TomHuntington 用覆盖和定点运算符来制定它,&gt;&gt;= 是覆盖的应用,apply_patch 是覆盖,定点运算符是“提取” monad,也就是说,一旦你完成了你的计算,你可能想通过计算实际结果从 monad 中提取值(即,把它想象成你想从Option 中实际获取一个值)包。
    • 另外,即使OptionResult 使用同样方便的宏?,它们也不是同一个monad。这似乎有点欺骗性,因为人们可能会期待很酷、花哨和复杂的 monad,但从理论上讲,最有用(因此也是最常用)的 monad 是非常基本的。你不会找到远离OptionResult 的广泛使用的monad。一个例外是 Nix,它的单子更加复杂。
    • 我仍然认为它们基本上是相同的单子,它们具有相同的短路行为
    【解决方案2】:
    猜你喜欢
    • 2020-11-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 2017-10-10
    • 1970-01-01
    相关资源
    最近更新 更多