【问题标题】:Haskell "No instance for" type error in simple code简单代码中的Haskell“无实例”类型错误
【发布时间】:2012-03-14 06:57:09
【问题描述】:

我是 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> 

我一直在用头撞墙,试图理解“No instance for (Num (m0 b0))”是什么意思。 m0 b0 到底是什么?

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

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

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

【问题讨论】:

  • 起初。在then (do ...) 和else (-1) 之后的if ... then do ... else -1 表达式有不同的类型。 do ... 具有一元类型 (m b)。 -1 的类型实现了 Num 类型类(换句话说,它只是一个数字)。你的代码应该做什么?
  • 不清楚您要做什么...为什么 sieve monadic ?您打算将其用于什么单子?至于错误,这意味着您的 if 的两个分支都不兼容: then 导致“sieve n (i+1)”必须是单子的,因为它是 do-block 的最后一个元素,所以说类型为“Monad” m => ma" 和 else 导致 "-1" 这显然是一个数字。数字字面量在 Haskell 中是多态的,这里编译器尝试将“-1”转换为“ma”类型并发现它不知道如何做到这一点(它只知道 Num 实例,即 Int、Integer、Float ...)。
  • 您是否尝试复制您注释掉的列表理解的行为?
  • 你想让sieve拥有什么类型?
  • 您试图在不理解语言的情况下进行命令式编写。 Haskell 是一种与您可能习惯的语言完全不同的语言。理解需要一些时间,但您必须首先忘记有关命令式语言的所有内容。

标签: haskell types type-conversion


【解决方案1】:

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

我对 Haskell 新手的建议是“避免使用 monads 和 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 的真正筛选器,因为您没有“标记”值。但是,这个筛子实际上是“更好”的,因为它既更快又定义了 all 素数的集合,而不是一个数字。这不是你实际应该如何生成素数的方式,但是在 6 行代码中(其中大部分是不必要的),很难说突变或循环使事情变得更简单。

【讨论】:

    【解决方案2】:

    这不仅会产生类型错误:

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

    但它的值与

    完全相同
    do {
       sieve n (i + 1);
       }
    

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

    【讨论】:

      【解决方案3】:

      Haskell 是一种函数式语言。这尤其意味着,您不告诉编译器 Do A then do B 而是 calculate A – 你不告诉编译器以什么顺序做事,而是告诉它你想知道的。您可以将其视为只有一个 (return) 语句。因此,您需要重写代码以适应此范例。

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

      正如其他人已经向您展示的那样,您可以做这样的事情。管道 (|) 称为 pattern guard,其工作原理类似于 If-then-else:

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

      请记住,您无法更改变量的值。你可能需要重写你的innerSieve 来返回一些东西。

      【讨论】:

        【解决方案4】:

        您的内部 do 块具有一元类型。这使得sieve 的整个结果具有(Monad m) =&gt; m b 的一元类型。你的if 语句的else 分支返回-1,所以我们知道它也有一个数字类型。这使您的函数的结果为(Num (m b), Monad m) =&gt; m b。这显然是错误的。

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

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

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

        【讨论】:

        • 基本上,我想调用innerSieve,然后我想再次递归筛子。
        • 为什么你打电话给innerSieve?你想用它计算的值做什么?
        • 我正在尝试重现erastothenes 的筛子。我认为这将是一个简单的挑战,直到我意识到 Haskell 没有循环结构。我推测我需要一个带有列表理解的递归函数
        猜你喜欢
        • 1970-01-01
        • 2013-01-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-27
        • 2013-04-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多