Haskell学习练习给出了奇怪的结果

时间:2010-01-08 12:40:02

标签: haskell

这是一个问题:

  

“写一个计算的函数   列表的平均值,即所有的总和   列表中的元素除以其   长度。 (您可能需要使用   fromInteral功能转换   整数列表的长度   到浮点数。)“

首先我尝试了这个:

mean :: [Double] -> Double
mean []     = 0
mean (x:xs) = (x + mean xs) / (1 + mean xs)

但它给了我奇怪的结果,例如,当我这样使用它时:

mean [2,4,6]  

它给我的结果:1.41176
它应该在哪里:4

为什么?

我尝试了另一件事:

mean :: [Double] -> Double  
mean list = (summ list) / (count list)  
    where count []     = 0  
          count (x:xs) = 1 + count xs  
          summ []      = 0  
          summ (x:xs)  = x + summ xs

但是当我尝试将文件加载到GHC时出现错误 错误是:

parse error on input 'count'  
Failed, modules loaded: none

再次,我做错了什么?

最后,我尝试了这个(成功):

mean :: [Double] -> Double

count []     = 0
count (x:xs) = 1 + count xs
summ  []     = 0
summ (x:xs)  = x + summ xs
mean list    = summ list / count list

它与上面的那个相同(使用'where'关键字),但它只在这里成功,而不是在上面的那个。
为什么呢?

非常感谢。

P.S。
我正在从这本书中学习 - 真实世界哈斯克尔 练习来自here - (向下滚动: - ))


谢谢你们所有人。 这是一件奇怪的事。 当我从这里复制并测试它时,第二个例子对我也有用。 我不知道为什么它昨天对我不起作用: - )

但我仍然不明白为什么第一个不起作用。 我认为它应该是那样的


(2 + mean [4,6]) / (1 + mean [4,6])
(4 + mean [6  ]) / (1 + mean [  6])
(6 + mean [   ]) / (1 + mean [   ])      

所以现在就像那样


(6 +  0        )    / (1 +  0          )  -- last recursion
(4 + (6 + 0)   )    / (1 + (1 + 0)     )
(2 + (4 + (6 + 0))) / (1 + (1 + (1 + 0))

现在应该是:12/3
不是吗? 我不明白的是什么? 谢谢你: - )。

7 个答案:

答案 0 :(得分:5)

  1. 您第一次尝试时得到的答案错误,因为您使用的公式不正确。垃圾进垃圾出。 (其他人已经介绍了这一点。)

  2. 您可能会收到一个解析错误,因为有些行正在使用空格而其他行正在使用制表符。或者您正在使用所有选项卡,但使用非标准选项卡宽度。

  3. 此处未使用或不需要缩进,因此不会出现空格-v-制表符问题。

答案 1 :(得分:2)

您的第一次尝试评估如下:

6 / 1
4 + 6 / 1 + 6
2 + (10/7) / 1 + (10/7)

这不是你想要的。

第二次尝试没问题。

答案 2 :(得分:2)

正确:

import Data.List (foldl')
mean :: Fractional a => [a] -> a
mean = uncurry (/) . foldl' (\(s, n) x -> (s + x, n + 1)) (0, 0)

mean [2,4,6] = uncurry (/) $ foldl' (...) (0, 0) [2,4,6]
             = uncurry (/) $ foldl' (...) (2, 1) [4,6]
             = uncurry (/) $ foldl' (...) (6, 2) [6]
             = uncurry (/) $ foldl' (...) (12, 3) []
             = uncurry (/) (12, 3)
             = 12 / 3
             = 4

不正确:

mean [] = 0
mean (x:xs) = (x + mean xs) / (1 + mean xs)

mean [6] = mean (6 : [])
         = (6 + mean []) / (1 + mean [])
         = (6 + 0) / (1 + 0)
         = 6
mean [4,6] = mean (4 : [6])
           = (4 + mean [6]) / (1 + mean [6])
           = (4 + 6) / (1 + 6)
           = 10/7
mean [2,4,6] = mean (2 : [4,6])
             = (2 + mean [4,6]) + (1 + mean [4,6])
             = (2 + 10/7) / (1 + 10/7)
             = 24/17

答案 3 :(得分:2)

说我们定义

naivemean l = sum l / fromIntegral (length l)

它有一些严重的限制。首先,该定义不包括Int

的列表
*Main> naivemean ([1] :: [Int])

<interactive>:1:0:
    No instance for (Fractional Int)
      arising from a use of `naivemean' at <interactive>:1:0-21
    Possible fix: add an instance declaration for (Fractional Int)
    In the expression: naivemean ([1] :: [Int])
    In the definition of `it': it = naivemean ([1] :: [Int])

其次,它为大型列表打击了堆栈:

*Main> naivemean [1..1000000]
*** Exception: stack overflow

此外,当单次传递时,它会在列表中进行两次传递,一次用于sum,另一次用于length

我们可以用

纠正所有这三个问题
{-# LANGUAGE BangPatterns #-}

mean :: (Real a, Fractional b) => [a] -> b
mean = go (toRational 0) 0
  where
    go !s !l []     = fromRational s / fromIntegral l
    go  s  l (x:xs) = go (s+.x) (l+1) xs
    s +. x = s + toRational x

Bang patterns强制严格评估标记的参数。如果没有刘海,上面的代码也会在给出一个长列表时吹掉堆栈,但出于不同的原因:例如l的惰性求值会生成一个长的未评估的表单链

0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + ...

在这种情况下,延迟评估以分配所有暂停计算的形式创建更多工作,而不是在每次迭代时简单地递增计数器。

作为初学者,fromRationaltoRational也可能令人困惑。考虑除法函数的类型:

*Main> :t (/)
(/) :: (Fractional a) => a -> a -> a

这意味着为同一类型的Fractional实例的任何两个值定义了除法。 Int不是以下类型之一:

*Main> (1::Int) / (2::Int)

<interactive>:1:0:
    No instance for (Fractional Int)
      arising from a use of `/' at <interactive>:1:0-18
    Possible fix: add an instance declaration for (Fractional Int)
    In the expression: (1 :: Int) / (2 :: Int)
    In the definition of `it': it = (1 :: Int) / (2 :: Int)

mean的一个定义应涵盖[Int][Float][Double]的所有内容,但如果没有有理位(且没有类型注释),则推断类型对于mean

*Main> :t mean
mean :: [Double] -> Double

因为除以列表的长度。

事实证明IntFloatDouble是类型类Real的实例,任何Real都可以转换为{{1} }}

Rational

*Main> :t toRational toRational :: (Real a) => a -> Rational 可能会转换为Rational

Fractional

最后,对于大型列表,我们也有可能溢出机器的双倍,但*Main> :t fromRational fromRational :: (Fractional a) => Rational -> a 给我们任意精度。

如果您具有C或Java等语言背景,可以自动提升类型以处理这些情况,那么您会发现Haskell类型系统的这种特殊不灵活性令人困惑和令人沮丧。我担心你只需要学会处理它。

完成所有这些后,我们现在可以

Rational

*Main> mean ([1..1000000] :: [Int])
500000.5

答案 4 :(得分:1)

警告:未经测试的代码。在你对mean的定义中,你需要准确地携带运行总和和运行长度,正如其他人所指出的那样,你不是那样做的。以下内容应该有效:

mean0 sum len [] = sum / len
mean0 sum len (x:xs) = mean0 (sum+x) (len+1) xs

此定义传递两个 accumulators ,分别用于运行总计和运行计数,当您沿列表递归时更新。当函数最终耗尽要处理的列表(基本情况)时,它只进行所需的计算。

要使用mean0,您只需编写

即可
mean0 0 0 [2, 4, 6]

正如您所看到的,为累加器提供初始值有点烦人,因此提供类似

的包装器是很常见的。
mean xs = mean0 0 0 xs

现在,你就像你想要的那样写mean [2, 4, 6]

答案 5 :(得分:1)

Haskell:最美丽的命令式语言

import Control.Monad.ST
import Data.STRef 
import Control.Monad

mean xs = s / l  
    where (s,l) = runST $ do{
       acc <- newSTRef (0,0);
       forM_ xs $ \x -> do{
          modifySTRef acc $  \(a,b) -> (x+a,1+b)
       };
       readSTRef acc }

答案 6 :(得分:0)

表示x = sum(x)/ fromIntegral(length(x))

表示[2.0,4.0,6.0]

当然,这必须改进(空列表,适用于双打......)。