如何在 do 块中有条件地绑定?
How do I conditionally bind in a do block?
我想在 do 块中实现以下目标:
do
if condition then
n0 <- expr0
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
但是 Haskell 给出编译错误,除非我这样做:
do
if condition then
n0 <- expr0
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
它看起来很冗长,尤其是当有很多共享绑定表达式时。如何让它更简洁?
实际上,我正在尝试执行以下操作:
do
if isJust maybeVar then
n0 <- f (fromJust maybeVar)
n1 <- expr1
n2 <- expr2
return (T (Just n0) n1 n2)
else
n1 <- expr1
n2 <- expr2
return (T Nothing n1 n2)
以下仍然编译失败:
do
n0 <- if isJust maybeVar then Just (f (fromJust maybeVar)) else Nothing
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
您可以“内联”条件:
do
n0 <- <b>if condition then expr0 else expr0'</b>
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
您可能应该在 return
表达式中使用方括号,因此 return (T n0 n1 n2)
.
然后您可以将 liftM3 :: Monad m => (a1 -> a2 -> a3 -> r) -> m a1 -> m a2 -> m a3 -> m r
的表达式重写为:
<b>liftM3</b> T (if condition then expr0 else expr0') expr1 expr2
因为 Haskell 是一种纯语言,计算表达式没有副作用。但是这里 if
...then
...else
最多会计算两个表达式之一。 IO a
本身也没有副作用,因为它是 "recipe" 来生成 a
.
编辑:对于你的第二个例子,它更复杂。
do
n0 <- if isJust maybeVar then <b>Just <$></b> (f (fromJust maybeVar)) else <b>pure Nothing</b>
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
所以这里我们在 monadic 上下文中使用 pure Nothing
到 "wrap" Nothing
,并且 Just <$>
将 Just
应用于内部的值monadic 上下文。
或 says, we can here use traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
:
do
n0 <- traverse f maybeVar
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
这是可行的,因为 Maybe
是可遍历的:对于 Just
我们遍历单个元素,对于 Nothing
它将 return Nothing
.
由于 OP 在评论中说 "I still have to think more on why your solution works",我想我会添加一些解释作为补充答案。
Haskell 的 if condition then x else y
语法实际上 而不是 几乎所有命令式语言中的标准 if/then/else 语句的模拟。它更类似于条件表达式语法(在 C 中视为 condition ? x : y
,或在 Python 中视为 x if condition else y
)。一旦你记住了这一点,其他一切就会自然而然地发生。
Haskell 中的 if condition then x else y
是 值 的表达式,而不是 语句 。 x
和 y
不是 "things to do" 基于 condition
是否为真,而只是两个不同的值;整个 if/then/else 表达式是一个等于 x
或等于 y
的值(取决于条件)。
考虑到这一点,让我们看一下 Willem Van Onsem 建议的工作版本:
do
n0 <- if condition then expr0 else expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
此处 if condition then expr0 else expr0'
完全位于单个 <-
语句的右侧。所以它是一个值的表达式,就像下面几行的 expr1
和 expr2
一样。它没有说从 expr0
绑定 n0
或从 expr0'
绑定 n0
,它只是 是 或者 expr0
或 expr0'
。包含 if/then/else 的 <-
语句表示要绑定 n0
,它从 single[无条件 绑定 =86=] 整体计算的值 if/then/else.
我们可以很容易地看到这一点,因为我们可以完全独立于 do 块声明一个等于 if/then/else 的顶级变量(假设 condition
、expr0
, 和 expr0'
是全局可用的)并用对此变量的引用替换 if/then/else。
foo = do
n0 <- z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then expr0 else expr0'
这里很明显 if/then/else 与 do 块中 n0
的绑定完全无关。
让我们将其与原始的非工作版本进行比较:
do
if condition then
n0 <- expr0
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
这是使用 if/then/else 和 语句 作为 then 和 else 部分。这不仅仅是 "being" 一个值或另一个值,而是对 "do" 说一件事或另一件事。 Haskell 的 if/then/else 不是这样工作的。整个 if/then/else 需要能够理解为单个值的表达式。
同样,如果我们想象尝试将 if/then/else 分解为单独的声明,这应该很清楚:
foo = do
z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then
n0 <- expr0
else
n0 <- expr0'
应该清楚,这没有任何意义。 then 和 else 部分不是独立的值表达式,它们只在 do 块内有意义。而且它们需要在 particular do 块内 foo
,这样 n0
就可以稍后在 return (T n0 n1 n2)
中使用。
由于 do 块的语句无论如何都会转换为表达式,您可能认为将语句作为 if 表达式的 then/else 部分 应该 有效。然而,将 do 块转换为表达式 "cuts across" 语句,所以这是行不通的。例如:
do n <- expr
rest
相当于:
expr >>= (\n -> rest)
如果您还不了解,我不会对此进行完整的技术解释,但希望您能看到 n
最终与 rest
的联系比它使用它在语句中绑定的 expr
(在更复杂的示例中,rest
表示 do 块的全部剩余内容)。没有单独的表达式只表示 n <- expr
部分,您可以将其放在 if/then/else 表达式的 then 或 else 部分中。
我想在 do 块中实现以下目标:
do
if condition then
n0 <- expr0
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
但是 Haskell 给出编译错误,除非我这样做:
do
if condition then
n0 <- expr0
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return T n0 n1 n2
它看起来很冗长,尤其是当有很多共享绑定表达式时。如何让它更简洁?
实际上,我正在尝试执行以下操作:
do
if isJust maybeVar then
n0 <- f (fromJust maybeVar)
n1 <- expr1
n2 <- expr2
return (T (Just n0) n1 n2)
else
n1 <- expr1
n2 <- expr2
return (T Nothing n1 n2)
以下仍然编译失败:
do
n0 <- if isJust maybeVar then Just (f (fromJust maybeVar)) else Nothing
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
您可以“内联”条件:
do
n0 <- <b>if condition then expr0 else expr0'</b>
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
您可能应该在 return
表达式中使用方括号,因此 return (T n0 n1 n2)
.
然后您可以将 liftM3 :: Monad m => (a1 -> a2 -> a3 -> r) -> m a1 -> m a2 -> m a3 -> m r
的表达式重写为:
<b>liftM3</b> T (if condition then expr0 else expr0') expr1 expr2
因为 Haskell 是一种纯语言,计算表达式没有副作用。但是这里 if
...then
...else
最多会计算两个表达式之一。 IO a
本身也没有副作用,因为它是 "recipe" 来生成 a
.
编辑:对于你的第二个例子,它更复杂。
do
n0 <- if isJust maybeVar then <b>Just <$></b> (f (fromJust maybeVar)) else <b>pure Nothing</b>
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
所以这里我们在 monadic 上下文中使用 pure Nothing
到 "wrap" Nothing
,并且 Just <$>
将 Just
应用于内部的值monadic 上下文。
或traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
:
do
n0 <- traverse f maybeVar
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
这是可行的,因为 Maybe
是可遍历的:对于 Just
我们遍历单个元素,对于 Nothing
它将 return Nothing
.
由于 OP 在评论中说 "I still have to think more on why your solution works",我想我会添加一些解释作为补充答案。
Haskell 的 if condition then x else y
语法实际上 而不是 几乎所有命令式语言中的标准 if/then/else 语句的模拟。它更类似于条件表达式语法(在 C 中视为 condition ? x : y
,或在 Python 中视为 x if condition else y
)。一旦你记住了这一点,其他一切就会自然而然地发生。
if condition then x else y
是 值 的表达式,而不是 语句 。 x
和 y
不是 "things to do" 基于 condition
是否为真,而只是两个不同的值;整个 if/then/else 表达式是一个等于 x
或等于 y
的值(取决于条件)。
考虑到这一点,让我们看一下 Willem Van Onsem 建议的工作版本:
do
n0 <- if condition then expr0 else expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
此处 if condition then expr0 else expr0'
完全位于单个 <-
语句的右侧。所以它是一个值的表达式,就像下面几行的 expr1
和 expr2
一样。它没有说从 expr0
绑定 n0
或从 expr0'
绑定 n0
,它只是 是 或者 expr0
或 expr0'
。包含 if/then/else 的 <-
语句表示要绑定 n0
,它从 single[无条件 绑定 =86=] 整体计算的值 if/then/else.
我们可以很容易地看到这一点,因为我们可以完全独立于 do 块声明一个等于 if/then/else 的顶级变量(假设 condition
、expr0
, 和 expr0'
是全局可用的)并用对此变量的引用替换 if/then/else。
foo = do
n0 <- z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then expr0 else expr0'
这里很明显 if/then/else 与 do 块中 n0
的绑定完全无关。
让我们将其与原始的非工作版本进行比较:
do
if condition then
n0 <- expr0
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
这是使用 if/then/else 和 语句 作为 then 和 else 部分。这不仅仅是 "being" 一个值或另一个值,而是对 "do" 说一件事或另一件事。 Haskell 的 if/then/else 不是这样工作的。整个 if/then/else 需要能够理解为单个值的表达式。
同样,如果我们想象尝试将 if/then/else 分解为单独的声明,这应该很清楚:
foo = do
z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then
n0 <- expr0
else
n0 <- expr0'
应该清楚,这没有任何意义。 then 和 else 部分不是独立的值表达式,它们只在 do 块内有意义。而且它们需要在 particular do 块内 foo
,这样 n0
就可以稍后在 return (T n0 n1 n2)
中使用。
由于 do 块的语句无论如何都会转换为表达式,您可能认为将语句作为 if 表达式的 then/else 部分 应该 有效。然而,将 do 块转换为表达式 "cuts across" 语句,所以这是行不通的。例如:
do n <- expr
rest
相当于:
expr >>= (\n -> rest)
如果您还不了解,我不会对此进行完整的技术解释,但希望您能看到 n
最终与 rest
的联系比它使用它在语句中绑定的 expr
(在更复杂的示例中,rest
表示 do 块的全部剩余内容)。没有单独的表达式只表示 n <- expr
部分,您可以将其放在 if/then/else 表达式的 then 或 else 部分中。