在 Haskell 中使用嵌套的 `where` 子句

Posted

技术标签:

【中文标题】在 Haskell 中使用嵌套的 `where` 子句【英文标题】:Using nested `where` clauses in Haskell 【发布时间】:2016-12-31 07:05:16 【问题描述】:

在学习 Haskell 时,我要做一个函数来返回给定数字的所有整数除数。因此,我使用两个嵌套的 where 子句创建了它,但它不起作用。

错误返回exs2.hs:49:24: Parse error in pattern: negRef / 2

divisors' :: (Integral a) => a -> [a]
divisors' x = divs x (x/2) [x]
  where
    divs ref 1 list = negDiv (-ref) (-2) ((-1):1:list)
    divs ref num list = if (mod ref num == 0) then divs ref (num-1) (num:list) else divs ref (num-1) list
      where
        negDiv negRef (negRef/2) negList = (negRef:(negRef/2):negList)
        negDiv negRef negNum negList = if (mod negRef negNum == 0) then negDiv (negNum-1) (negNum:negList) else negDiv (negNum-1) negList

那有什么问题呢?它似乎缩进很好。

【问题讨论】:

negRef/2 不是有效模式。 内部 where 子句仅适用于 div 定义中的第二行。当第一行 div 调用 negDiv 时,内部 where 子句不在作用域内。那和模式的事情。 那我怎样才能正确地写出这种划分模式呢? @MathematicalOrchid @flamenquino 你不能;你需要使用保护:negDiv x y negList | x == y/2 = (x:y:negList) 其他情况被指定为额外的保护,因为模式 xy 将匹配任何东西。 【参考方案1】:

你有几个问题:

    您只能对文字和数据构造函数进行模式匹配,而不是像 / 这样的任意函数。 / 仅针对 Fractional a 值定义,而不是 Integral。请改用divnegDiv 的定义在递归调用中缺少参数。不过,尚不清楚这些论点应该是什么。

大部分更正的版本:

divisors' :: (Integral a) => a -> [a]
divisors' x = divs x (x `div` 2) [x]
  where
    divs ref 1 list = negDiv (-ref) (-2) ((-1):1:list)
    divs ref num list | ref `mod` num == 0 = divs ref (num-1) (num:list) 
                      | otherwise          = divs ref (num-1) list
    -- Three arguments, but only two given to each recursive call
    negDiv x y negList | x == negRef `div` 2 = x:y:negList
                       | x `mod` y == 0      = negDiv (y-1) (y:negList)
                       | otherwise           = negDiv (y-1) negList

顺便说一句,这样做更简单

divisors' x = ds ++ (map negate ds)               -- positive and negative ds
              where ds = filter (divs x) [1..x]   -- d such that d divides x
                    x `divs` y = x `mod` y == 0   -- Does y divide x?

甚至

divisors' x = [d | d <- [(-x)..x], d /= 0, x `mod` d == 0]

每当您发现自己编写递归函数来迭代列表时,您可能都忽略了正确的高阶函数或列表推导。

【讨论】:

【参考方案2】:

另一个问题是/ 运算符不适用于整数。在 Haskell 中,/ 是字段的除法运算符,因此需要 Fractional 类型,例如 RationalDouble

对于 integer 除法,您应该改用 div or quot

【讨论】:

我以前用div做过,但它返回了同样的错误。【参考方案3】:

您的第二个where-子句不使用divs 范围内的任何名称。你可以像这样只使用一个子句:

divisors' :: (Integral a) => a -> [a]
divisors' x = divs x (x/2) [x]
  where
    divs ref 1 list = negDiv (-ref) (-2) ((-1):1:list)
    divs ref num list = if (mod ref num == 0) then divs ref (num-1) (num:list) else divs ref (num-1) list
    negDiv negRef (negRef/2) negList = (negRef:(negRef/2):negList)
    negDiv negRef negNum negList = if (mod negRef negNum == 0) then negDiv (negNum-1) (negNum:negList) else negDiv (negNum-1) negList

如果你真的想用嵌套子句来表达你的函数,你可以使用let ... in

但在这种情况下,这没有用,我建议使用where 子句(这通常比let ... in 更受欢迎,在大多数情况下被认为不那么惯用)。

它不起作用的原因是该子句附加到divs 的第二个等式,而不是使用negDiv 的第一个等式。

PS:由于MathematicalOrchid said,negRef/2 不是一个有效的模式,这就是你的错误的来源。

【讨论】:

有时where 优于let,有时则相反。我不认为一个比另一个更惯用。例如,请参阅***.com/questions/4362328/haskell-where-vs-let。

以上是关于在 Haskell 中使用嵌套的 `where` 子句的主要内容,如果未能解决你的问题,请参考以下文章

Haskell - 在“where”中定义一个带有守卫的函数

Haskell:具有多个***绑定的“where”子句?

在 Haskell 中写或不写 `module Main where`

Haskell:绑定模式匹配的地方

在 Haskell 中重写嵌套 for 循环的好技术是啥?

如何在 Haskell 中嵌套 let 语句?