Haskell类型令人沮丧,简单的“平均”功能

时间:2010-03-04 05:00:32

标签: haskell types typeclass

我正在和初学者Haskell一起玩,我想写一个普通的函数。这似乎是世界上最简单的事情,对吗?

错误。

似乎Haskell的类型系统禁止平均处理泛型数字类型 - 我可以使它在Integrals列表或Fractionals列表上工作,但不能同时工作。

我想:

average :: (Num a, Fractional b) => [a] -> b
average xs = ...

但我只能得到:

averageInt :: (Integral a, Fractional b) => [a] -> b
averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs)

averageFrac :: (Fractional a) => [a] -> a
averageFrac xs = sum xs / fromIntegral (length xs)

第二个似乎有效。直到我尝试传递变量。

*Main> averageFrac [1,2,3]
2.0
*Main> let x = [1,2,3]
*Main> :t x
x :: [Integer]
*Main> averageFrac x

<interactive>:1:0:
    No instance for (Fractional Integer)
      arising from a use of `averageFrac ' at <interactive>:1:0-8
    Possible fix: add an instance declaration for (Fractional Integer)
    In the expression: average x
    In the definition of `it': it = averageFrac x

显然,Haskell对它的类型非常挑剔。那讲得通。但不是当他们都可以[Num]

我错过了RealFrac的明显应用吗?

有没有办法将积分强制转化为分数,当它获得分数输入时不会窒息?

是否有某种方法可以使用Eithereither来制作某种适用于任何数字数组的多态平均函数?

Haskell的类型系统是否完全禁止此功能存在?

学习Haskell就像学习微积分一样。它真的很复杂,基于理论的山脉,有时问题是如此令人难以置信的复杂,我甚至不知道如何正确地表达问题,所以任何见解都会被热烈接受。

(另外,脚注:这是基于家庭作业问题。每个人都同意上面的averageFrac获得满分,但我有一种潜在的怀疑,即有一种方法可以使它在Integral和Fractional数组上工作)< / p>

5 个答案:

答案 0 :(得分:91)

所以从根本上说,你受到(/)类型的限制:

(/) :: (Fractional a) => a -> a -> a

BTW,你也想要Data.List.genericLength

genericLength :: (Num i) => [b] -> i

那么如何删除fromIntegral以获得更通用的内容:

import Data.List

average xs = realToFrac (sum xs) / genericLength xs

只有一个Real约束(Int,Integer,Float,Double)......

average :: (Real a, Fractional b) => [a] -> b

这样就可以将任何Real带入任何分数。

并注意所有海报都被Haskell中的多态数字文字所捕获。 1不是整数,它是任何数字。

Real类只提供一种方法:能够将类Num中的值转换为有理数。这正是我们在这里所需要的。

因此,

Prelude> average ([1 .. 10] :: [Double])
5.5
Prelude> average ([1 .. 10] :: [Int])
5.5
Prelude> average ([1 .. 10] :: [Float])
5.5
Prelude> average ([1 .. 10] :: [Data.Word.Word8])
5.5

答案 1 :(得分:23)

Dons已经很好地回答了这个问题,我想我可能会添加一些内容。

以这种方式计算平均值时:

average xs = realToFrac (sum xs) / genericLength xs

你的代码将做的是遍历列表两次,一次计算其元素的总和,一次得到它的长度。 据我所知,GHC还无法对此进行优化,只需一次计算总和和长度。

即使作为初学者考虑它和可能的解决方案也没有什么坏处,例如,平均功能可能使用折叠来计算总和和长度;在ghci:

:set -XBangPatterns

import Data.List

let avg l=let (t,n) = foldl' (\(!b,!c) a -> (a+b,c+1)) (0,0) l in realToFrac(t)/realToFrac(n)

avg ([1,2,3,4]::[Int])
2.5
avg ([1,2,3,4]::[Double])
2.5

该功能看起来不那么优雅,但性能更好。

有关Dons博客的更多信息:

http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-c-working-at-a-high-altitude-for-low-level-performance/

答案 2 :(得分:7)

由于dons在回答你的问题方面做得非常好,我会努力质疑你的问题....

例如,在您的问题中,您首先在给定列表上运行平均值,获得一个好的答案。然后,你看起来像完全相同的列表,将它分配给一个变量,然后使用函数变量......然后它会爆炸。

你在这里遇到的是编译器中的设置,称为DMR: D readed M onomorphic R estriction。当您直接将列表传递给函数时,编译器不会假设数字是哪种类型,它只是根据使用情况推断出它可以是什么类型,然后在不再缩小字段时选择一个。这有点像鸭子打字的直接对立面。

无论如何,当您将列表分配给变量时,DMR就会启动。由于您已将列表放在变量中,但没有给出如何使用它的提示,DMR使编译器选择了一个类型,在这种情况下,它选择了一个与表格匹配并且似乎合适的:Integer。由于您的函数无法在/操作中使用Integer(它需要Fractional类中的类型),因此非常抱怨:Integer中没有Fractional的实例{1}}课程。您可以在GHC中设置选项,这样它就不会强制您将值转换为单一形式(“mono-morphic”,得到它?),直到需要为止,但它会使任何错误消息更难以理解。 / p>

现在,在另一张纸条上,你回答了让我眼前一亮的答案:

  

我被cs.ut.ee/~varmo/MFP2004/PreludeTour.pdf最后一页的图表误导了。   这显示浮动NOT从Real继承属性,然后我假设   他们不会共享任何类型的共同点。

Haskell的类型与您习惯的类型不同。 RealFloating是类型类,它们更像界面而不是对象类。它们告诉你你可以用该类中的类型做什么,但这并不意味着某些类型不能做其他事情,只不过有一个接口意味着(n OO风格)类不能还有其他人。

  

学习Haskell就像学习微积分一样

我会说学习Haskell就像学习瑞典语一样 - 有许多小的,简单的东西(字母,数字)看起来和工作方式相同,但也有一些看起来像是应该意味着一件事的话,当他们实际意味着别的东西。但是一旦你精通它,你的常规朋友会惊讶于你如何能够喷出这些古怪的美女做出惊人的技巧。奇怪的是,从一开始就有许多人参与Haskell,他们也熟悉瑞典语。也许这个比喻不仅仅是一个隐喻......

答案 3 :(得分:3)

:m Data.List
let list = [1..10]
let average = div (sum list) (genericLength list)
average

答案 4 :(得分:-6)

是的,Haskell的类型系统非常挑剔。这里的问题是fromIntegral的类型:

Prelude> :t fromIntegral
fromIntegral :: (Integral a, Num b) => a -> b

fromIntegral将接受积分作为,而不是任何其他类型的数字。 (/),另一方面只接受分数。你如何让这两个人一起工作?

总和函数是一个好的开始:

Prelude> :t sum
sum :: (Num a) => [a] -> a

Sum获取任何Num的列表并返回Num。

你的下一个问题是列表的长度。长度是Int:

Prelude> :t length
length :: [a] -> Int

您还需要将该Int转换为Num。这就是来自整合的。

所以现在你有一个返回Num的函数和另一个返回Num的函数。对于数字you can look up的类型推广有一些规则,但基本上在这一点上你很高兴:

Prelude> let average xs = (sum xs) / (fromIntegral (length xs))
Prelude> :t average
average :: (Fractional a) => [a] -> a

让我们试一试:

Prelude> average [1,2,3,4,5]
3.0
Prelude> average [1.2,3.4,5.6,7.8,9.0]
5.4
Prelude> average [1.2,3,4.5,6,7.8,9]
5.25