Haskell 输入一个简单的“普通”函数

我和初学者 Haskell 一起玩,我想写一个平均函数。这似乎是世界上最简单的事情,对吧?

错了。

Haskell 的类型系统似乎禁止 average 处理泛型数字类型——我可以让它处理一个 Integrals 列表,或者一个分数类型列表,但不能同时处理这两个类型。

我想要:

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 获得满分,但是我有一个潜在的怀疑,那就是有一种方法可以让它同时在整数和分数数组上工作)

40116 次浏览

因此,从根本上说,您受到(/)类型的约束:

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

顺便说一下,您还需要 Data.List.genericLlength

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

那么,为了更一般的东西,去掉 from Integral 怎么样:

import Data.List


average xs = realToFrac (sum xs) / genericLength xs

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

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

所以它将把任何 Real 分解成任何分数形式。

注意所有在哈斯克尔被多态数字字面值捕获的海报。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

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

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

From Integral 将 只有接受一个整数作为 a,而不是任何其他类型的 Num。(/) ,另一方面只接受小数。你是如何让这两者协同工作的呢?

求和函数是个好的开始:

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

Sum 获取任意 Num 的列表并返回一个 Num。

您的下一个问题是列表的长度:

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

你还需要把 Int 转换成 Num,from Integral 就是这么做的。

现在您已经得到了一个返回 Num 的函数和另一个返回 Num 的函数。对于数字 你可以往上看的类型提升,有一些规则,但基本上在这一点上,你可以去:

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

这个问题已经得到了很好的回答,我想我可以补充一些东西。

这样计算平均值:

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/

既然老师们已经很好地回答了你的问题,我将努力提出你的问题... ..。

例如,在你的问题中,你首先在给定的列表中运行一个平均值,得到一个好的答案。然后,你拿着看起来完全一样的名单,把它赋给一个变量,然后使用函数变量... 然后爆炸。

您在这里遇到的是编译器中的一个设置,称为 DMR: D读取的 M经典 R约束。当你将列表直接传递给函数时,编译器不会假设数字的类型,它只是根据用法推断它可以是什么类型,然后在无法再缩小字段范围时选择一个。有点像鸭子打字的反义词。

无论如何,当您将列表分配给一个变量时,DMR 启动了。因为您已经将列表放入了一个变量中,但是没有给出关于如何使用它的提示,所以 DMR 让编译器选择一种类型,在本例中,它选择了一种与形式匹配且似乎适合的类型: Integer。因为你的函数不能在它的 /操作中使用 Integer (它需要在 Fractional类中使用类型) ,所以它非常抱怨: 在 Fractional类中没有 Integer的实例。您可以在 GHC 中设置一些选项,这样它就不会强迫您的值成为单一的形式(“ mono-formic”,明白了吗?)直到它需要的时候,但是它使得任何错误消息稍微难以识别。

现在,另一方面,你的回答引起了我的注意:

我被 cs.ut.ee/~varmo/mfp2004/preludetour.pdf 最后一页的图表误导了 显示了从 Real 继承属性的 他们没有共同的类型。

Haskell 的输入方式与您所习惯的不同。RealFloating是类型类,它们的工作方式更像接口而不是对象类。它们告诉您可以对该类中的类型执行哪些操作,但这并不意味着某些类型不能执行其他操作,就像拥有一个接口意味着(n OO 风格)类不能拥有任何其他接口一样。

学习 Haskell 就像学习微积分

我认为学习 Haskell 就像学习瑞典语一样——有很多小的、简单的东西(字母、数字)看起来和实际上是一样的,但也有一些单词看起来应该表示一件事情,而实际上它们表示的是另外一件事情。但是一旦你熟练掌握了,你的普通朋友就会惊讶于你是如何滔滔不绝地说出这些奇怪的东西的,这些东西能让美丽的姑娘们做出惊人的表演。奇怪的是,很多人从一开始就在哈斯克尔,他们也懂瑞典语。也许这个比喻不仅仅是个比喻。

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

我很惊讶,这么多年过去了,还没有人指出唐 · 斯图尔特的 average不适用于复数,而 OP 的 averageFrac 是的适用于复数。两者都没有明显的优越性。

你不能写作的根本原因

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

它可以在类似于

average :: [Complex Double] -> Double

Haskell 的数值类支持有一点损耗的转换,比如 RationalDoubleDoubleFloatIntegerInt,但是不支持极其有损耗的转换,比如复数到实数,或者分数到整数。如果没有显式地获取(例如) Complex Double的真实部分,就无法将 Complex Double转换为 Double,而这不是 average应该做的事情。因此,不能编写 average :: [Complex Double] -> Double。因此,不能使用任何专门针对 Double1的类型来编写 average

最哈斯克式的 average可能是 OP 的 averageFrac。通常,不专门用于类型转换的函数应该尽可能将类型转换留给调用方。averageFrac实际上可以处理任何数字类型,可以直接处理,也可以在强制输入列表之后处理。调用者离数据源越近,就越有可能知道是否需要强制(如果不知道,可以让调用者自己做决定)。相比之下,唐•斯图尔特(Don Stewart)的 average就是不支持复杂的数字,即使是在强制下也是如此。您可以从头开始重写它,或者使用列表的真实和虚拟投影调用它两次(然后为调用它四次的四元数编写另一个包装器,等等)。