Haskell简单代码中的“没有实例”类型错误

时间:2012-02-23 20:02:16

标签: haskell types type-conversion

我对Haskell来说是一个非常大的菜鸟。

我有这段代码:

4 sieve n i = if i < n
5             then
6                 do {
7                  innerSieve n;
8                  sieve n (i + 1);
9                 }
10             else -1
11 
12 innerSieve n = return n
13 
14 --innerSieve n i j = [x | x <- [i..n], x `mod` j == 0]

我有这个错误:

[1 of 1] Compiling Main             ( sieve.hs, interpreted )
Ok, modules loaded: Main.
*Main> sieve 10 2

<interactive>:1:1:
No instance for (Num (m0 b0))
  arising from a use of `sieve'
Possible fix: add an instance declaration for (Num (m0 b0))
In the expression: sieve 10 2
In an equation for `it': it = sieve 10 2
*Main> 

我一直在撞墙试图理解“没有实例(Num(m0 b0))的含义。”世界上有什么是m0 b0?

我认为这可能会有所帮助:

*Main> :t sieve
sieve :: (Ord a, Num a, Num (m b), Monad m) => a -> a -> m b
*Main> 

编辑: 我试图通过基本上使用列表理解来创建具有列表理解的递归函数来重新创建erastothenes的筛子。我也想 - 理解代码中的所有内容。

4 个答案:

答案 0 :(得分:5)

您的内部do块具有monadic类型。这使得sieve的整个结果具有(Monad m) => m b的monadic类型。 else语句的if分支返回-1,因此我们知道它也有数字类型。这会使您的函数(Num (m b), Monad m) => m b得到结果。这显然是错误的。

我无法弄清楚你的功能想要完成什么,所以我不确定你到底出错了什么。我建议在函数上编写显式类型注释,以准确表达您期望每个函数执行的操作。这将至少为您提供更好的错误消息,因为它不是说推断类型没有意义,而是指向您确切的表达式与显式类型不匹配。

顺便说一下,您可能会发现if语句更好地表示为

sieve n i
    | i < n     = -- contents of the then block
    | otherwise = -1

答案 1 :(得分:4)

Haskell是一种功能语言。这尤其意味着,你不要告诉编译器做A然后做B 而是计算A - 你不要告诉编译器用什么顺序做事情,而不是你告诉它你想知道什么。您可以将其视为只有一个(return)语句。因此,您需要重写代码以适应此范例。

do语句用于称为 monad 的特殊构造,它基本上允许以一致的方式执行有状态操作(例如打印到屏幕)。你不需要它。

正如其他人已经告诉你的那样,你可以做这样的事情。管道(|)被称为模式保护,基本上像If-then-else一样工作:

sieve n i | i < n     = --result if condition is met
          | otherwise = -1

请记住,无法更改变量的值。您可能需要重写innerSieve以返回内容。

答案 2 :(得分:3)

正如其他人所说,你几乎肯定不想在代码中使用do块。 do用于Monads,在学习Haskell时应该避免使用,并且可能不合适,因为Monad这里只有[]会忽略任何 innerSieve的定义。 Haskell代码根据其他值和类型描述和类型。 Haskell程序不告诉计算机该做什么,他们告诉它。这是一种激进的思维方式,但却是一种非常强大的思维方式。 do表示法是一种处理monad的特殊语法,monad是一种特殊的值,可用于干净有效地编码计算(包括命令式计算和状态转换),但它需要广泛了解Haskell才能很好地使用。

我对Haskell新手的建议是“避免monad和do表示法,直到你掌握:递归,高阶函数和类型类。你不需要do来测试你的程序(使用GHCi),所以要学习真实的思维方式。尝试编程使你的程序不do任何东西!“

那么,你怎么会在Haskell写一个Eratosthenes的筛子呢?有很多优雅的方式。警告:前方的破坏者。如果你想自己这样做,请停止阅读

您已注释掉:

innerSieve n i j = [x | x <- [i..n], x `mod` j == 0]

我不确定ni的重点是什么。写

会更优雅
innerSieve j ls = [x | x <- ls, x `mod` j == 0]

,其类型为

innerSieve :: Integral x => x -> [x] -> [x]
换句话说,它使列表具有某种类型x的值,使得该类型可以被视为整数,并返回原始值的所有整数倍。

您可以使用它来编写Eratosthenes的筛子,但更好的方法是获得倍数的所有值。原因是,那么你可以将一堆这些堆叠在一起以生产完整的主筛

innerSieve j ls = [x | x <- ls, x `mod` j /= 0]

这里/=是Haskell说“不平等”的方式。很酷,所以让我们建立一个主要的筛子

sieve (x:xs) = x : sieve (innerSieve x xs)
sieve [] = []

这是什么意思?它需要一个数字列表,并返回一个由这些数字中的第一个组成的新列表,并且筛选应用于其余数字,除了那些是第一个数字的倍数。 (x:xs)是一个模式,它匹配除空列表以外的任何列表,将x与列表的头部绑定,xs与列表的尾部匹配名单。 []是匹配任何空列表的模式。

最后,您可以定义所有素数的无限列表

primes = sieve [2..]

表达式[2..]生成永远存在的列表2,3,4,5,6,7,etc。看起来很可怕(无限列表),但由于Haskell着名的懒惰评估,这是可以的。要获得第n个素数,你可以说:

nthPrime n = primes !! (n - 1)

只是索引到素数列表。 Haskell只计算返回结果所需的最小值,因此即使primes无限长,此函数也会终止

或将所有素数加到数字

primesUpToN n = take n primes

因此,您的整个代码库最终成为:

sieve [] = []
sieve (x:xs) = x : sieve (innerSieve x xs) where
   innerSieve j ls = [x | x <- ls, x `mod` j /= 0]

primes = sieve [2..]

nthPrime n = primes !! (n - 1)

primesUpToN n = take n primes

这不是Eratosthenes的真正筛子,因为你没有“标记”价值观。但是,这个筛子实际上“更好”,因为它既快又定义了所有素数的集合,而不是那些达到数字的素数。这不是你应该如何实际生成素数,而是在6行代码中(大多数是不必要的),很难说变异或循环使事情变得更简单。

答案 3 :(得分:0)

这不仅会产生类型错误:

do {
   innerSieve n;
   sieve n (i + 1);
   }

但它与

具有完全相同的值
do {
   sieve n (i + 1);
   }

你已经调用了一个函数“innerSeive”,但是没有对值进行任何操作 如果您想计算一个值,然后在下一个操作中使用该值,请使用(>>=)运算符(在let x = innerseive n; f2 n;块中加为do)。
但是,对于这些数学函数,请远离dolet(>>=)等。