我试图使用折叠来取得Haskell中整数之和的平方。但是,我从GHCi那里得到了一个神秘的错误。这是我的单行:
((^2) . foldl) (+) 0 [1..100]
我从GHCi获得的是:
Prelude> ((^2) . foldl) (+) 0 [1..100]
<interactive>:19:3:
No instance for (Num (b0 -> [b0] -> b0))
arising from a use of `^'
Possible fix:
add an instance declaration for (Num (b0 -> [b0] -> b0))
In the first argument of `(.)', namely `(^ 2)'
In the expression: (^ 2) . foldl
In the expression: ((^ 2) . foldl) (+) 0 [1 .. 100]
我认为问题出在我最后根据此类型声明传入的列表中。
Prelude> :t ((^2) . foldl) (+) 0 [1..100]
((^2) . foldl) (+) 0 [1..100]
:: (Enum b, Num b, Num (b -> [b] -> b)) => b
任何人都可以让我深入了解为什么这种类型期望Enum
以及任何显式转换列表以便我可以调试此函数的方法吗?提前谢谢。
答案 0 :(得分:9)
是的,这仍然是GHC产生的最荒谬和无用的错误信息之一。
首先,忽略该消息,考虑foldl
和(.)
的类型:
foldl :: (a -> b -> a) -> a -> [b] -> a
(.) :: (b -> c) -> (a -> b) -> a -> c
请注意(.)
仅使用第一个参数进行编写。由于currying,具有“多个”参数的函数实际上是一个返回另一个函数的参数的函数。因此,在表达式((^2) . foldl)
中,foldl
的“返回类型”为a -> [b] -> a
,这是它尝试与(^ 2)
合成的内容。
由于错误消息是愚蠢的,它会抱怨Num
没有a -> [b] -> a
个实例,以便用(^2) :: Num a => a -> a
撰写,并建议您添加一个。{/ p>
你想要的是这样的:((^2) . foldl (+) 0)
。也就是说,使用(懒惰)foldl
可能是一个坏主意。最好使用严格foldl'
或更好的内置sum
函数:(^2) . sum
此外,类型中提到的Enum
约束是无关紧要的,事实上是正确的 - Enum
类型类提供了用于解释范围表示法的函数。因此(Enum b, Num b) => ...
表示b
是可以枚举的数字类型,这正是表达式[1 .. 100]
所需的。
答案 1 :(得分:5)
正确的撰写方式是(^2) . foldl (+) 0 :: Num a => [a] -> a
或(^2) . foldl (+) 0 $ [1..100] :: (Num a, Enum a) => a
。当您单独执行(^2) . foldl
时,您会尝试对第一个返回类型foldl
的参数求平方,这是一个函数a -> [a] -> a
。该错误声明了这一点:它没有针对此类函数的Num
实例,因此它无法调度适当的幂函数(^)
。
通常,将组合(.)
视为仅适用于单个输入函数的组件。类型表明了这个
(.) :: (b -> c) -> (a -> b) -> a -> c
虽然它有更多的一般用途,但它们有点难以找到。
所以我的解决方案有效,因为我在编写之前直接将参数应用于foldl
。这些参数使foldl (+) 0 :: Num a => [a] -> a
成为单个输入参数的函数。
答案 2 :(得分:0)
已经给出了很好的答案,但是编写它可能更直接
(^2) $ foldl (+) 0 [1..100]
表达式foldl (+) 0 [1..100]
本身就有效,如果你试图了解foldl的工作方式,那么这会有所帮助,然后将结果传递给(^2)
。写成函数
f list = (^2) $ foldl (+) 0 list