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

jak*_*man 68 haskell types typeclass

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

错误.

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

我想要:

average :: (Num a, Fractional b) => [a] -> b
average xs = ...
Run Code Online (Sandbox Code Playgroud)

但我只能得到:

averageInt :: (Integral a, Fractional b) => [a] -> b
averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs)
Run Code Online (Sandbox Code Playgroud)

要么

averageFrac :: (Fractional a) => [a] -> a
averageFrac xs = sum xs / fromIntegral (length xs)
Run Code Online (Sandbox Code Playgroud)

而第二个似乎工作.直到我尝试传递变量.

*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
Run Code Online (Sandbox Code Playgroud)

显然,Haskell对其类型非常挑剔.那讲得通.但不是什么时候他们都可以[Num]

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

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

有没有办法使用Eithereither制作某种多态平均函数,可以在任何类型的数值数组上工作?

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

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

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

Don*_*art 95

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

(/) :: (Fractional a) => a -> a -> a
Run Code Online (Sandbox Code Playgroud)

顺便说一句,你也想要Data.List.genericLength

genericLength :: (Num i) => [b] -> i
Run Code Online (Sandbox Code Playgroud)

那么如何删除fromIntegral以获得更通用的东西:

import Data.List

average xs = realToFrac (sum xs) / genericLength xs
Run Code Online (Sandbox Code Playgroud)

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

average :: (Real a, Fractional b) => [a] -> b
Run Code Online (Sandbox Code Playgroud)

所以这将把任何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
Run Code Online (Sandbox Code Playgroud)

  • 你可以提供双打列表,因为Double是真实的."平均([1 ... 10] :: [双])".Real类恰好增加了从Num中的东西构造理性值的能力.这正是你所需要的. (5认同)
  • 编写numToFrac是不可能的,因为Num不提供任何转换功能.Real是我们最接近的东西(可以转换为Rational的Num类型),或者Integral(可以转换为无界整数的Num类型). (4认同)

Dav*_* V. 25

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
Run Code Online (Sandbox Code Playgroud)

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

有关Dons博客的更多信息:

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

  • 对于Robert Massaioli评论+1,因为这个avg实际上非常糟糕... foldl'在累加器中是严格的,对于Haskell来说基本上意味着"弱头正常形式",严格到第一个数据构造函数.因此,例如,foldl'将保证累加器的评估足以确定它是一对(第一个数据构造函数),但内容将不会被评估,因此累积thunk.使用`foldl'(\(!b,!c)a - > ...`或严格的对类型`data P a = P!a!a`是在这种情况下获得良好性能的关键. (8认同)
  • +1表示折叠以获得更好的性能提升,但我只建议在完全理解Haskell后尝试性能,并且整数的多态性对于你来说是孩子的游戏. (3认同)

BMe*_*eph 7

既然dons在回答你的问题方面做得很好,我会努力质疑你的问题....

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

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

无论如何,当您将列表分配给变量时,DMR就会启动.由于您已将列表放在变量中,但没有给出如何使用它的提示,DMR使编译器选择了一个类型,在此例如,它选择了一个与表格匹配并且似乎合适的:Integer.由于在您的函数不能使用整数/操作(它需要在一个类型Fractional类),它使这很抱怨:有没有实例IntegerFractional类.您可以在GHC中设置选项,以便它不会将您的值强制为单个形式("mono-morphic",得到它?),直到需要为止,但它会使任何错误消息更难以弄清楚.

现在,在另一个说明中,你得到了一个引起我注意的回答:

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

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

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

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