【问题标题】:Do-notation and the list monadDo-notation 和列表单子
【发布时间】:2021-12-27 11:52:27
【问题描述】:

我正在学习 Haskell。

我正在尝试查找列表 as 的元素,这些元素与列表 bs 的元素相加,将元素作为元组返回:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = [(a, a', b) | a <- as, a' <- as, b <- bs, a + a' == b]

代码有效。但是为了学习Haskell,我正在尝试将其重写为do-notation:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  if a + a' == b then return (a, a', b) 
                 else return ()

然后类型检查器向我抱怨:

   • Couldn't match type ‘()’ with ‘(Int, Int, Int)’
      Expected type: [(Int, Int, Int)]
        Actual type: [()]

平心而论,我知道会的。但是既然我不能跳过 Haskell 中的else 子句,那我应该在else 子句中的return 语句中放入什么?

谢谢。

【问题讨论】:

  • 在列表 monad 中,return (a,a',b) 表示单例列表 [(a,a',b)],它表示“我找到了一个三元组,(a,a',b),将其添加到结果列表中”。同样,return () 表示 [()],即“我找到了一个三元组,(),将其添加到结果列表中”,因为这不是三元组,所以会触发类型错误。要表示“这次我没有找到三元组”,请使用空列表 [](没有任何 return)。
  • 啊。我试过return [],但显然这也是错误的。我想我还在为do-notation 苦苦挣扎。您是如何到达可以识别何时自动返回的地方的?
  • 你必须“思考类型”——在 Haskell 中,这是最重要的技能之一。您正在使用单子(列表单子[]),因此您需要记住什么是随机值以及什么是单子动作。 return 不是一个神奇的原语,而是一个将任何值 x 转换为内部只有该值的无聊单子动作的函数(单例列表 [x],在这个单子中)。如果你有x = [],你真的需要再次将它包装在一个列表中吗?
  • 比较列表推导 [ a | a &lt;- [1,2,3] ][ a | a &lt;- [[1,2,3]] ]。在前者中,a 的范围超过1,2,3,而在后者中,a 只有一个值,列表[1,2,3]。那是因为我们添加了一个额外的[.....]。我们可能不希望这样,这与您在[]return [](即[[]])之间发现的问题相同。在列表 monad 中,仅使用 return 表示“找到一个值”,而不是“此处未找到值”。
  • 好的,所以我今天发现了另一个,那就是 RETURN 仅适用于类型。该提示很有用,并帮助我走上了一条我一直在怀疑但今天才设法确认的道路。但是要确认一下:当您查看一个函数并且它在do-block 中显示return 时,您是否也会自动盯着类型签名以了解正在调用哪个return? (这就是我现在开始做的事情。)

标签: list haskell list-comprehension monads do-notation


【解决方案1】:

您必须在else 子句中返回正确类型的内容。它可以是空列表[],也可以是mzeroempty 等抽象值之一。

或者您可以删除if 表达式并使用guard 函数。

import Control.Monad (guard)

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  guard (a + a' == b)
  return (a, a', b)

通过此实现,您现在还可以将函数签名概括为:

findSum2 :: MonadPlus m => m Int -> m Int -> m (Int, Int, Int)

【讨论】:

  • 谢谢!我从 guard 抽象中学到了一些新东西。这看起来确实像邪恶的命令式编程,虽然呵呵
【解决方案2】:

你不能返回单位(()),因为这意味着return (a, a', b)return ()有不同的类型:第一个是[(Int, Int, Int)],而后者是[()]

如果守卫失败,你可以做的是使用一个空列表,所以:

findSum2 :: [Int] -> [Int] -> [(Int,Int,Int)]
findSum2 as bs = do
  a  <- as
  a' <- as 
  b  <- bs
  if a + a' == b then return (a, a', b) else []

【讨论】:

    猜你喜欢
    • 2014-03-26
    • 2021-08-14
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 1970-01-01
    • 2018-09-05
    • 2013-05-19
    相关资源
    最近更新 更多