在 Haskell 中匹配明显相似的类型时出现问题
Problem matching apparently similar types in Haskell
这个小程序可以很好地处理 Int
的具体类型,但我想尝试使该函数成为具有类型约束的最通用的函数。
问题是我不明白错误信息,因为两个“刚性类型”似乎具有相同的约束,但无法匹配。
module PotentiationNew where
import Data.Bits (FiniteBits, testBit, finiteBitSize, countLeadingZeros)
potentiation :: (Num a, Integral a, FiniteBits b) => a -> b -> a -> a
potentiation base exponent modulus =
potentiationGo $ getBits exponent
where
potentiationGo :: (Num c, Integral c) => [Bool] -> c
potentiationGo [] = 1
potentiationGo (currentExp : restExp)
| currentExp = (x * x * base) `mod` modulus -- This is the line with the error
| otherwise = (x * x) `mod` modulus
where x = potentiationGo restExp
-- Gets a bit array represented as [Bool] (True is 1)
-- Bit order starts with the least significant bits.
getBits :: FiniteBits a => a -> [Bool]
getBits x = testBit x <$> indexed
where
indexed = [0 .. finiteBitSize x - countLeadingZeros x - 1]
错误信息如下:
Potentiation.hs:13:22: error:
• Couldn't match expected type ‘c’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
potentiation :: forall a b.
(Num a, Integral a, FiniteBits b) =>
a -> b -> a -> a
at Potentiation.hs:6:17
‘c’ is a rigid type variable bound by
the type signature for:
potentiationGo :: forall c. (Num c, Integral c) => [Bool] -> c
at Potentiation.hs:10:23
仅仅因为两种类型具有相同的约束并不意味着它们是同一类型。例如,Int
是Num
,Integer
是Num
,但是Int
和Integer
是不一样的。
在 potentiationGo
中,您使用值 modulus
和 base
,它们都是 a
类型,在您的类型签名表明应该存在的位置c
类型的值。这两种类型不一样!
解决问题的最简单方法是删除 potentiationGo
的类型签名,这应该会删除错误,但仍保持一般性(GHC 非常擅长自动派生最一般的类型)。
就是说,如果您像我一样,您真的很喜欢显式类型签名,因为至少,它可以作为一种很好的健全性检查,并使您的代码更加自文档化。在这种情况下,您真正想要写的是 potentiationGo :: [Bool] -> a
,其中 a
与 potentiation
本身的类型签名中的 a
相同。如果你尝试这样做,它不会起作用,因为 GHC 默认为每个签名创建新的类型变量。为了解决这个问题,您需要启用 ScopedTypeVariables
,这会让 GHC 知道两个 a
是相同的。这也意味着您需要使用 forall
关键字引入类型 a
(一旦您使用 forall
,您需要引入类型中的每个类型变量)。它看起来像这样:
{-# LANGUAGE ScopedTypeVariables #-}
potentiation :: forall a b. (Num a, Integral a, FiniteBits b) => a -> b -> a -> a
potentiation base exponent modulus =
potentiationGo $ getBits exponent
where
potentiationGo :: [Bool] -> a -- Because the `a` above is in scope and we didn't
-- write forall here, this a refers to the above one.
...
这是您能得到的最一般的。
请注意,这不会影响您的 getBits
函数,因为 getBits
并未直接使用值 exponent
。相反,该值作为参数传递。按照这种逻辑,您还可以通过编写类似以下内容来修复 potentiationGo
:
potentiation base' exponent modulus' =
potentiationGo base' modulus' $ getBits exponent
where
potentiationGo :: (Num c, Integral c) => c -> c -> [Bool] -> c
potentiationGo base modulus [] = 1
potentiationGo base modulus (currentExp : restExp) = ...
注意类型 a
的值现在如何作为参数提供给 potentiationGo
。在 potentiationGo 中,值的类型为 c
。类型 c
和 a
永远不会统一为相等,但是因为 a
是 Num
和 Integral
,它可以传递给 potentiationGo
作为c
.
这个小程序可以很好地处理 Int
的具体类型,但我想尝试使该函数成为具有类型约束的最通用的函数。
问题是我不明白错误信息,因为两个“刚性类型”似乎具有相同的约束,但无法匹配。
module PotentiationNew where
import Data.Bits (FiniteBits, testBit, finiteBitSize, countLeadingZeros)
potentiation :: (Num a, Integral a, FiniteBits b) => a -> b -> a -> a
potentiation base exponent modulus =
potentiationGo $ getBits exponent
where
potentiationGo :: (Num c, Integral c) => [Bool] -> c
potentiationGo [] = 1
potentiationGo (currentExp : restExp)
| currentExp = (x * x * base) `mod` modulus -- This is the line with the error
| otherwise = (x * x) `mod` modulus
where x = potentiationGo restExp
-- Gets a bit array represented as [Bool] (True is 1)
-- Bit order starts with the least significant bits.
getBits :: FiniteBits a => a -> [Bool]
getBits x = testBit x <$> indexed
where
indexed = [0 .. finiteBitSize x - countLeadingZeros x - 1]
错误信息如下:
Potentiation.hs:13:22: error:
• Couldn't match expected type ‘c’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
potentiation :: forall a b.
(Num a, Integral a, FiniteBits b) =>
a -> b -> a -> a
at Potentiation.hs:6:17
‘c’ is a rigid type variable bound by
the type signature for:
potentiationGo :: forall c. (Num c, Integral c) => [Bool] -> c
at Potentiation.hs:10:23
仅仅因为两种类型具有相同的约束并不意味着它们是同一类型。例如,Int
是Num
,Integer
是Num
,但是Int
和Integer
是不一样的。
在 potentiationGo
中,您使用值 modulus
和 base
,它们都是 a
类型,在您的类型签名表明应该存在的位置c
类型的值。这两种类型不一样!
解决问题的最简单方法是删除 potentiationGo
的类型签名,这应该会删除错误,但仍保持一般性(GHC 非常擅长自动派生最一般的类型)。
就是说,如果您像我一样,您真的很喜欢显式类型签名,因为至少,它可以作为一种很好的健全性检查,并使您的代码更加自文档化。在这种情况下,您真正想要写的是 potentiationGo :: [Bool] -> a
,其中 a
与 potentiation
本身的类型签名中的 a
相同。如果你尝试这样做,它不会起作用,因为 GHC 默认为每个签名创建新的类型变量。为了解决这个问题,您需要启用 ScopedTypeVariables
,这会让 GHC 知道两个 a
是相同的。这也意味着您需要使用 forall
关键字引入类型 a
(一旦您使用 forall
,您需要引入类型中的每个类型变量)。它看起来像这样:
{-# LANGUAGE ScopedTypeVariables #-}
potentiation :: forall a b. (Num a, Integral a, FiniteBits b) => a -> b -> a -> a
potentiation base exponent modulus =
potentiationGo $ getBits exponent
where
potentiationGo :: [Bool] -> a -- Because the `a` above is in scope and we didn't
-- write forall here, this a refers to the above one.
...
这是您能得到的最一般的。
请注意,这不会影响您的 getBits
函数,因为 getBits
并未直接使用值 exponent
。相反,该值作为参数传递。按照这种逻辑,您还可以通过编写类似以下内容来修复 potentiationGo
:
potentiation base' exponent modulus' =
potentiationGo base' modulus' $ getBits exponent
where
potentiationGo :: (Num c, Integral c) => c -> c -> [Bool] -> c
potentiationGo base modulus [] = 1
potentiationGo base modulus (currentExp : restExp) = ...
注意类型 a
的值现在如何作为参数提供给 potentiationGo
。在 potentiationGo 中,值的类型为 c
。类型 c
和 a
永远不会统一为相等,但是因为 a
是 Num
和 Integral
,它可以传递给 potentiationGo
作为c
.