【问题标题】:How do I conditionally bind in a do block?如何有条件地绑定在 do 块中?
【发布时间】:2020-01-05 00:08:00
【问题描述】:

我想在 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)

【问题讨论】:

  • 你写的是if ... then ... then,而不是if ... then ... else
  • if isJust maybeVar then Just (f (fromJust maybeVar)) else Nothing 拼写更好f &lt;$&gt; maybeVar

标签: haskell conditional-statements bind monads do-notation


【解决方案1】:

您可以“内联”条件:

do 
  n0 <- if condition then expr0 else expr0'
  n1 <- expr1
  n2 <- expr2
  return (T n0 n1 n2)

您可能应该在return 表达式中使用方括号,所以return (T n0 n1 n2)

然后您可以使用liftM3 :: Monad m =&gt; (a1 -&gt; a2 -&gt; a3 -&gt; r) -&gt; m a1 -&gt; m a2 -&gt; m a3 -&gt; m r 将表达式重写为:

<b>liftM3</b> T (if condition then expr0 else expr0') expr1 expr2

由于 Haskell 是一种评估表达式的纯语言,因此没有副作用。但这里 if...then...else 最多将评估两个表达式之一。 IO a 本身也没有副作用,因为它是生成 a 的“秘诀”。

编辑:对于您的第二个示例,它更复杂。

do 
    n0 <- if isJust maybeVar then Just <$> (f (fromJust maybeVar)) else pure Nothing
    n1 <- expr1
    n2 <- expr2
    return (T n0 n1 n2)

所以这里我们使用pure Nothing 在单子上下文中“包装”Nothing,并在单子上下文中使用Just &lt;$&gt; 对值应用Just

或者如@luqui所说,我们可以在这里使用traverse :: (Traversable t, Applicative f) =&gt; (a -&gt; f b) -&gt; t a -&gt; f (t b)

do 
    n0 <- traverse f maybeVar
    n1 <- expr1
    n2 <- expr2
    return (T n0 n1 n2)

这是可行的,因为 Maybe 是可遍历的:对于 Just,我们遍历单个元素,对于 Nothing,它将返回 Nothing

【讨论】:

  • 为了更准确,我添加了更多上下文。请告知我做错了什么。
  • 你刚刚成就了我的一天。我仍然需要更多地思考您的解决方案为何有效,但它绝对是一种魅力!
  • “由于 Haskell 的懒惰”——这有点牵强。即使他们被热切地评估,但这并不意味着他们也会被执行
  • @leftaroundabout:是的,我同意。我重写了那部分。
  • 顺便说一句,第二个例子可以写成n0 &lt;- traverse f maybeVar(虽然我喜欢你出于教学原因展示它的方式)
【解决方案2】:

由于 OP 在评论中说“我仍然需要更多地考虑为什么您的解决方案有效”,我想我会添加一些解释作为补充答案。

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 yvalue 的表达式,而不是 statementxy 不是基于 condition 是否为真的“要做的事情”,而只是 2 个不同的值;整个 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' 完全位于单个&lt;- 语句的右侧。所以它是一个值的表达式,就像expr1expr2 在以下几行中一样。它没有说从expr0 绑定n0 或从expr0' 绑定n0,它只是 expr0expr0'。包含 if/then/else 的 &lt;- 语句是绑定 n0 的内容,它无条件地从整个 if 计算的 single 值中绑定它/然后/否则。

我们可以很容易地看到这一点,我们可以声明一个等于 if/then/else 的顶级变量,完全独立于 do 块(假设 conditionexpr0expr0' 是全局可用)并将 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 部分。这不仅仅是“成为”一种或另一种价值,而是说“做”一件事或另一件事。这不是 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 块中才有意义。并且它们需要位于foo 中的特定 do 块内,以便n0 绑定以供以后在return (T n0 n1 n2) 中使用。

由于 do 块的语句无论如何都会转换为表达式,因此您可能认为将语句作为 if 表达式的 then/else 部分应该起作用。然而,将 do 块转换为表达式“跨越”语句,所以这不起作用。比如这个:

do  n <- expr
    rest

等价于:

expr >>= (\n -> rest)

如果您还不了解,我不会对此进行完整的技术解释,但希望您能看到 n 最终与 rest 的联系比与 @987654357 的联系更紧密@ 它是从语句中绑定的(rest 表示 do 块的全部剩余内容,在更复杂的示例中)。没有一个表达式只代表 n &lt;- expr 部分,您可以将其放在 if/then/else 表达式的 then 或 else 部分中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-12
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    • 2019-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多