如何处理 Haskell 中的类型

How to deal with types in Haskell

我已经开始学习 Haskell 并且一直在阅读 "Learn You a Haskell for Great Good"。我读了一半的模块章节。一位朋友向我展示了 codewars,我决定将我学到的一些知识用于测试。

我正在尝试创建一个函数,该函数 returns 是一个布尔值,用于判断给定的 Integral 是否是 4 的幂。这是代码。

module PowerOfFour where

isWhole n = fromIntegral (round n) == n

isPowerOf4 :: Integral n => n -> Bool
isPowerOf4 4 = True
isPowerOf4 x = if x < 4 then
    False
else
    if isWhole (x / 4) then
    isPowerOf4 (truncate (x / 4))
  else
    False

这是我收到的错误消息。

/tmp/haskell11524-8-kke52v/PowerOfFour.hs:10:12:
    Could not deduce (RealFrac n) arising from a use of `isWhole'
    from the context (Integral n)
      bound by the type signature for
                 isPowerOf4 :: Integral n => n -> Bool
      at /tmp/haskell11524-8-kke52v/PowerOfFour.hs:5:15-37
    Possible fix:
      add (RealFrac n) to the context of
        the type signature for isPowerOf4 :: Integral n => n -> Bool
    In the expression: isWhole (x / 4)
    In the expression:
      if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False
    In the expression:
      if x < 4 then
          False
      else
          if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False

/tmp/haskell11524-8-kke52v/PowerOfFour.hs:10:23:
    Could not deduce (Fractional n) arising from a use of `/'
    from the context (Integral n)
      bound by the type signature for
                 isPowerOf4 :: Integral n => n -> Bool
      at /tmp/haskell11524-8-kke52v/PowerOfFour.hs:5:15-37
    Possible fix:
      add (Fractional n) to the context of
        the type signature for isPowerOf4 :: Integral n => n -> Bool
    In the first argument of `isWhole', namely `(x / 4)'
    In the expression: isWhole (x / 4)
    In the expression:
      if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False

我做错了什么?

我在 isWhole 中使用 fromIntegral 修复了类似的错误,现在 isWhole 单独工作正常。但是,我似乎无法摆脱 isPowerOf4 中的这些错误。我已经尝试了 GHC 提供的可能的修复,但我可能没有做对。

我宁愿保留 isPowerOf4 函数的类型签名,因为它是由 codewars 提供的,所以我猜这是一个要求。

Haskell 从不,曾经 进行隐式类型转换。这对大多数程序员来说似乎相当顽固,但实际上这是 Haskell 的类型系统如此出色的原因之一。完全避免转换使很多事情变得简单得多,并且这是完全双向类型推断的先决条件。

你的情况,问题出在x / 4。一个除法,你似乎期望结果可能是非整数。但是必须是整数,只要是整数类型1即可!所以需要先显式转换为小数类型2。最简单的方法确实是您已经发现的 fromIntegral 函数:

   if isWhole $ fromIntegral x / 4 then ...

为了清楚起见:它被解析为 if isWhole ( (fromIntegral x)/4 ) then。此时你可能想知道:如果我只是说 Haskell 从不隐式转换东西,那么为什么除以整数文字 4 就可以了?答案是 Haskell 没有整数文字!它只有各种 数字文字 。整数文字没有任何特定类型,它是多态的(即,它从上下文请求此处预期的类型,并且比该类型表现得更好——前提是类型在 Num class).


1C 通过对结果进行整数除法截断来“解决”这个问题......这会导致大量问题,可能比你更多d 通过隐式转换得到。

2“一些小数类型”的意思是,在Haskell中,它会默认为Double。这种类型非常快,但也不是完全没有问题,因为浮点数本质上是不精确的。我现在不确定这是否会成为您应用程序中的问题;因为 == 比较,它很可能是。基本上,您需要指定 isWhole 的类型以防止出现这种情况,如 isWhole :: Rational -> BoolRational 类型将非整数表示为精确分数。

在 Haskell 中,/ 运算符用于小数类型而不是整数类型。对于整数类型,你应该使用 div (你可以通过用反引号包围它来使用它中缀)。您也可以使用 modrem 求余数,例如典型语言中的 % (负数不同)

isPowerOf4 :: Integral n => n -> Bool
isPowerOf4 4 = True
isPowerOf4 x = if x < 4
  then False
  else
    if x `mod` 4 == 0
      then isPowerOf4 (x `div` 4)
      else False

正如@leftaroundabout 所解释的那样,/ 是浮点除法,结果可能存在舍入误差。因此,您的 isWhole 函数不能保证在所有情况下都能正常工作。

按照@Jubobs 的建议,更好的方法是使用整数除法和取模。

div 是整数除法,它为您提供除余数的除法结果。这与您对 truncate (x / 4) 的期望相同。这应该重写为 div x 4,因此它不依赖于浮点数。

对于isWhole,您可以使用模数,这是整数除法的其余部分。 (例如 mod 5 4 == 1)如果它是零则没有余数,结果是一个整数。您可以使用 mod x 4 == 0 而不是 isWhole (x / 4)divmod 都工作准确,因此没有舍入错误的危险。