我想学习F#,但有一件事让我感到困惑的是计算表达式(do-notation??)语法和脱糖。
在 haskell 中,你有一个非常简单的 Monad 类型类和规则,用于将 do-notation 脱糖为绑定和返回。添加关键字没有魔力;唯一必须匹配的是类型。
在 F# 中,有一堆生成器、关键字和复杂性。
对于如何将一个概念映射到另一个概念,是否有很好的解释?
我基本上想知道我是如何映射的
do
x <- monadicComputation
foo x
someOtherMonadicComputation
let y = somePureComputation x
return $ bar y
到 F#。
Haskell中唯一的关键字是do,(<-)和let。
不能在 F# 中编写泛型 monadic 代码,而是必须通过命名与表达式关联的生成器来指定正在使用的 monad。您的示例代码如下所示:
let example = async {
let! a = someAsyncComputation
foo a
do! someOtherAsyncComputation
let y = somePureComputation a
return (bar y)
}
对于async
计算表达式类型。"bang"模式(do!,let!等)用于绑定一元值,而常规关键字用于非一元值。
let!
对应于绑定(>>=)
,而let
对应于do
表示法中的let
。 return
对应于return
,而return!
用于产生现有的一元值。 do!
类似于(>>)
,它为其效果执行一元值,而do
用于非一元效应,这在 Haskell 中没有相似之处。
如果你来自Haskell背景,那么你可能会对我最近写的一篇关于F#计算表达式的学术文章感兴趣。
它将计算表达式语法(非常灵活)链接到 Haskell 中使用的标准类型类。如前所述,F# 不容易让你在 monad 上编写泛型代码(可以做到,但它不是惯用的),但另一方面,它允许你选择最合适的语法,你甚至可以获得 MonadPlus
或 monad 转换器的漂亮语法。
除了Lee提到的async
monad之外,这里还有一个MonadPlus
的例子(使用序列表达式 - 一个列表monad - 作为示例):
let duplicate list = seq {
for n in list do
yield n
yield n ∗ 10 }
或者解析器的计算表达式:
let rec zeroOrMore p = parse {
return! oneOrMore p
return [] }
haskell do 符号只有一个特殊的语法,即映射到bind
函数的<-
,里面的其他一切都只是普通的函数应用程序,其结果是 monad 类型,例如:return
、putStr
等。
同样,在 F# 中,您必须let!
来表示bind
操作并return
关键字语法糖(不像 haskell 那样的普通函数调用,但此关键字映射到您定义的Return
函数)。现在,您的计算表达式可以支持许多其他关键字(如果不需要,您可以轻松省略它们),它们都记录在这里。这些附加操作为您提供了使用 F# 关键字而不是返回 monadic 值的普通函数的语法糖。可以看到,可以在 F# 计算表达式中重载的所有关键字都具有 monadic 返回值。
因此,基本上,您无需担心所有这些关键字,只需将它们视为普通的 monad 返回函数(具有可在文档中找到的特定类型签名),您可以在计算表达式语法中使用 F# 关键字调用这些函数。