我有一个关于haskell中单子的本体论问题;我对语言是否完全区分语句和表达式感到不安。例如,我觉得在大多数其他语言中,任何带有签名的东西a -> SomeMonadProbs ()
都会被视为陈述。也就是说,由于 haskell 是纯函数式的,并且函数是由表达式组成的,所以我对 haskell 会根据表达式引擎对 monad 说些什么感到有点困惑。
3 回答
Monad
只是与表达式交互的一个接口。例如,考虑使用do
符号实现的列表推导:
example :: [(Int, Int)]
example = do
x <- [1..3]
y <- [4..6]
return (x, y)
这意味着:
[1..3] >>= \x ->
[4..6] >>= \y ->
return (x, y)
...并代入(>>=)
for 列表的定义给出:
concatMap (\x -> concatMap (\y -> [(x, y)]) [4..6]) [1..3]
重要的想法是,您可以使用do
符号执行的任何操作都可以替换为对(>>=)
.
Haskell 中最接近“语句”的是do
符号块的句法行,例如:
x <- [1..3]
这些行不对应于孤立的表达式,而是对应于非自包含表达式的句法片段:
[1..3] >>= \x -> ... {incomplete lambda}
所以说一切都是 Haskell 中的表达式真的更合适,do
符号给你的东西看起来像一堆语句,但实际上是在底层的一堆表达式中去糖的。
这里有一些想法。
a >>= b
是一个应用程序,就像任何其他应用程序一样,所以从句法的角度来看,Haskell 中显然没有语句,只有表达式。
从语义的角度来看(参见例如解决尴尬的小队论文),Haskell 语义有“指称”和“操作”片段。
指称片段>>=
与数据构造函数类似,因此它被认为a >>= b
是在 WHNF 中。“操作”片段“解构”了 IO monad 中的值,并在过程中执行不同的效果。
在推理程序时,您通常根本不需要考虑“操作”片段。例如,当您重构foo a >> foo a
为let bar = foo a in bar >> bar
您不关心 的性质时foo
,因此 IO 操作与此处的任何其他值没有区别。
这就是 Haskell 的闪光点,很容易说根本没有陈述,但它会导致有趣且有点自相矛盾的结论。例如,C预处理器语言可以被认为是C的一个指称片段。所以C也有指称片段和操作片段,但没有人说C是纯粹的函数式或没有语句。有关此问题的详细处理,请参阅The C language is pure functional post。
Haskell 当然在数量上与 C 不同:它的指称片段具有足够的表达力,可以在实际中使用,因此您必须比 C 更少地考虑其操作语义中的潜在转换。
但是当您必须考虑这些转换时,例如在推理写入网络套接字的数据顺序时,您必须诉诸于语句后语句的思维方式。
因此,虽然 IO 动作本身不是语句,并且在某种狭义的技术意义上根本没有语句,但动作代表语句,所以我认为可以公平地说语句以非常间接的形式出现在 Haskell 中。
语言是否完全区分语句和表达式
它不是。语法中没有“陈述”或类似的东西,在语言描述中没有任何东西被称为“陈述”或任何等价的东西(据我所知)。
语言报告将符号内的元素称为do
“语句”。有两种不是表达式的语句:pat <- exp`` and
let decls`。
在大多数其他语言中,任何带有签名的东西
a -> SomeMonadProbs ()
都将被视为声明
Haskell 与大多数其他语言不同。这就是它的重点(显然,不是为了它而不同,而是将表达式和语句统一到一个结构中)。