Haskell质数列表推导函数报错问题求助
问题背景
我是Haskell新手,有C语言基础,目前以《Learn You a Haskell for Great Good》为学习资料,正在学习列表推导。因为能轻松用C编写质数程序,所以尝试用Haskell写出如下代码:
primes = [x | x <- [2..100], null [f | f <- [2..round (x/2)], 0 == rem x f]] -- 生成100以内的质数列表,`f`表示因子 -- 原理:只把没有除了1和自身之外因子的数加入主列表
错误现象
编译时错误
将代码保存为train.hs编译时,抛出大量类型相关错误:
train.hs:84:20: No instance for (Enum t0) arising from the arithmetic sequence ‘2 .. 100’ The type variable ‘t0’ is ambiguous Relevant bindings include primes :: [t0] (bound at train.hs:84:1) Note: there are several potential instances: instance forall (k :: BOX) (s :: k). Enum (Data.Proxy.Proxy s) -- Defined in ‘Data.Proxy’ instance Integral a => Enum (GHC.Real.Ratio a) -- Defined in ‘GHC.Real’ instance Enum Ordering -- Defined in ‘GHC.Enum’ ...plus 8 others In the expression: [2 .. 100] In a stmt of a list comprehension: x <- [2 .. 100] In the expression: [x | x <- [2 .. 100], null [f | f <- [2 .. round (x / 2)], 0 == rem x f]] train.hs:84:21: No instance for (Num t0) arising from the literal ‘2’ The type variable ‘t0’ is ambiguous Relevant bindings include primes :: [t0] (bound at train.hs:84:1) Note: there are several potential instances: instance Integral a => Num (GHC.Real.Ratio a) -- Defined in ‘GHC.Real’ instance Num Integer -- Defined in ‘GHC.Num’ instance Num Double -- Defined in ‘GHC.Float’ ...plus three others In the expression: 2 In the expression: [2 .. 100] In a stmt of a list comprehension: x <- [2 .. 100] train.hs:84:49: No instance for (Integral t0) arising from a use of ‘round’ The type variable ‘t0’ is ambiguous Relevant bindings include x :: t0 (bound at train.hs:84:15) primes :: [t0] (bound at train.hs:84:1) Note: there are several potential instances: instance Integral Integer -- Defined in ‘GHC.Real’ instance Integral Int -- Defined in ‘GHC.Real’ instance Integral Word -- Defined in ‘GHC.Real’ In the expression: round (x / 2) In the expression: [2 .. round (x / 2)] In a stmt of a list comprehension: f <- [2 .. round (x / 2)] train.hs:84:57: No instance for (Fractional t0) arising from a use of ‘/’ The type variable ‘t0’ is ambiguous Relevant bindings include x :: t0 (bound at train.hs:84:15) primes :: [t0] (bound at train.hs:84:1) Note: there are several potential instances: instance Integral a => Fractional (GHC.Real.Ratio a) -- Defined in ‘GHC.Real’ instance Fractional Double -- Defined in ‘GHC.Float’ instance Fractional Float -- Defined in ‘GHC.Float’ In the first argument of ‘round’, namely ‘(x / 2)’ In the expression: round (x / 2) In the expression: [2 .. round (x / 2)] train.hs:84:65: No instance for (Eq t0) arising from a use of ‘==’ The type variable ‘t0’ is ambiguous Relevant bindings include f :: t0 (bound at train.hs:84:40) x :: t0 (bound at train.hs:84:15) primes :: [t0] (bound at train.hs:84:1) Note: there are several potential instances: instance (Eq a, Eq b) => Eq (Either a b) -- Defined in ‘Data.Either’ instance forall (k :: BOX) (s :: k). Eq (Data.Proxy.Proxy s) -- Defined in ‘Data.Proxy’ instance (GHC.Arr.Ix i, Eq e) => Eq (GHC.Arr.Array i e) -- Defined in ‘GHC.Arr’ ...plus 28 others In the expression: 0 == rem x f In a stmt of a list comprehension: 0 == rem x f In the first argument of ‘null’, namely ‘[f | f <- [2 .. round (x / 2)], 0 == rem x f]’ Failed, modules loaded: none.
GHCi交互环境错误
直接在GHCi中输入代码后,调用primes时出现不同错误:
Prelude> let primes = [x | x <- [2..100], null [f | f <- [2..round (x/2)], 0 == rem x f]] Prelude> primes <interactive>:12:1: No instance for (Integral t0) arising from a use of ‘it’ The type variable ‘t0’ is ambiguous Note: there are several potential instances: instance Integral Integer -- Defined in ‘GHC.Real’ instance Integral Int -- Defined in ‘GHC.Real’ instance Integral Word -- Defined in ‘GHC.Real’ In the first argument of ‘print’, namely ‘it’ In a stmt of an interactive GHCi command: print it
修改方案与疑问解答
根据教材提示,需要做两处修改:
- 声明
primes为整数列表:primes :: [Integer] - 修改因子列表生成部分:
f <- [2..round (fromIntegral x/2)]
疑问1:为什么primes需要声明类型,而listCom不用?
看这段可以正常运行的代码:
listCom = [2*x | x <- [1..50], rem x 3 == 0]
原因是Haskell的类型推断在listCom中能明确唯一类型:
rem要求两个参数是Integral类型,[1..50]的序列类型也被约束为Integral- 没有冲突的类型要求,GHC可以自动推断出
listCom :: Integral a => [a],在运行时会默认用Integer类型(或根据上下文调整)
但你的primes代码中存在类型冲突:
x / 2要求x是Fractional类型(因为/是分数除法)rem x f要求x是Integral类型- 同时满足
Fractional和Integral的类型只有Ratio a(分数类型),这不是你想要的,且GHC无法确定你想用哪种类型,所以抛出类型歧义错误。
给primes加上:: [Integer]的类型声明后,明确了x是Integer(属于Integral),消除了歧义。
疑问2:为什么已经用了round,还要加fromIntegral?
x是Integer类型(Integral类),而/运算符只接受Fractional类型的参数。fromIntegral的作用是把Integral类型的值转换为更通用的Num类型,这样就能自动适配为Fractional类型(比如Double)来进行除法运算。
如果不加fromIntegral,x / 2会报错,因为Integer不是Fractional类型,不能直接用/。round只是把除法结果转换回整数,但前提是除法运算本身能合法执行——所以必须先用fromIntegral x把x转成可以做除法的类型。
另外,你可以用整数除法运算符div简化代码,这样就不需要fromIntegral和round了:
primes :: [Integer] primes = [x | x <- [2..100], null [f | f <- [2..x `div` 2], 0 == rem x f]]
补充测试分析:为什么ii=33; samLis=[2..round(ii/2)]能运行?
因为ii被GHC推断为Double类型了——当你写ii=33时,GHC会根据后续的ii/2(需要Fractional)自动把ii的类型推断为Double,而Double既是Fractional也是Enum(支持序列生成),所以[2..round(ii/2)]可以正常运行。
但在你的primes代码中,x来自[2..100],同时又要参与rem x f(需要Integral),这就导致了类型冲突,无法自动推断出一个同时满足Integral和Fractional的合理类型,所以必须手动明确类型或转换。
内容的提问来源于stack exchange,提问作者Suraj

