多范式语言的有用单子
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
上,如果 a
是 Some(x)
。相反,它会让当前函数好像是那个闭包,让它直接在 x
.
上运行
到目前为止,还不错。但是 Rust 有其他具有这种单子模式的预定义类型,这不在您的原始问题中。例如,Result<T, E>
。在 Rust 中,你通常希望避免 throw
、panic!
、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 Suspense is to a Monad as Hooks are to Applicative Notation
一旦我理解了 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
上,如果 a
是 Some(x)
。相反,它会让当前函数好像是那个闭包,让它直接在 x
.
到目前为止,还不错。但是 Rust 有其他具有这种单子模式的预定义类型,这不在您的原始问题中。例如,Result<T, E>
。在 Rust 中,你通常希望避免 throw
、panic!
、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 Suspense is to a Monad as Hooks are to Applicative Notation