【问题标题】:How to properly define an Haskell function isPrime?如何正确定义 Haskell 函数 isPrime?
【发布时间】:2025-12-05 05:15:02
【问题描述】:

我正在尝试创建一个基本函数来测试 Haskell 中整数的素数。我的代码可以在特定意义上工作,但是当我尝试将它传递给函数时继续收到错误消息。请注意,我使用:{:} 直接在GHCi 中编写定义。

这个想法是创建一个以 N 为模{所有整数到四舍五入的 sqrt (N)} 的列表,然后测试结果列表中除初始索引之外的零。以下四个函数都起作用:

rndRoot :: (Floating a, Integral c, RealFrac a) => a -> c
rndRoot = round . sqrt

oneToRndRoot :: (Floating a, Integral t, RealFrac a) => a -> [t]
oneToRndRoot x = [1..rndRoot(x)]

modulo x y
  | n < 0 = x
  | otherwise = modulo n y
  where n = x - y

mapMod x = map (modulo x)

这也有效:

mapMod 49 (oneToRndRoot 49)

然而,虽然 repl 毫无怨言地接受了这个定义......

mapModToRndRoot x = mapMod x $ oneToRndRoot x

...当我尝试使用它时它会吐出以下错误消息:

Prelude> mapModToRndRoot 39

<interactive>:475:1:
    No instance for (Floating a0) arising from a use of ‘it’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Floating Double -- Defined in ‘GHC.Float’
      instance Floating Float -- Defined in ‘GHC.Float’
    In the first argument of ‘print’, namely ‘it’
    In a stmt of an interactive GHCi command: print it

似乎工作正常的临时解决方案只是使用两个参数而不是重复相同的参数

mapModToRndRoot2 x y = map (modulo x) (oneToRndRoot y)
Prelude> mapModToRndRoot2 33 33
[0,1,0,1,3,3]

【问题讨论】:

  • 您使用的是哪个版本的 GHC?我问是因为我得到的错误与你的略有不同。 GHCi 输入中的lets 在问题的原始版本中(并且我已经编辑掉了......)可能表明您没有使用 GHC 8。
  • 7.10.3,我会升级
  • 虽然如果可以升级是个好主意,但请注意,您仍然会遇到同样的问题,正如 Fried Brice 所解释的那样,只是出现了不同的错误消息。 (我只询问了 GHC 版本,以确保在撰写答案时不会遗漏任何可能相关的细节。)
  • 为了提高效率,您最好避免在整数和浮点数之间切换,方法是将 oneToRndRoot 中的条件 $n

标签: haskell discrete-mathematics number-theory


【解决方案1】:

检查mapModToRndRoot 的类型给出以下信息:

mapModToRndRoot :: (Floating a, Integral a, RealFrac a) => a -> [a]

因此,要调用mapModToRndRoot 39,我们需要为文字39 分配一个数字类型,它是RealFracIntegralFloating 的一个实例。但是,Prelude 数值类型都不满足所有这三个约束,所以我们得到一个编译错误。

另一方面,mapMod 49 (oneToRndRoot 49) 工作正常。注意以下 lambda 的类型:

\x y -> mapMod x (oneToRndRoot y)
  :: (RealFrac a, Integral b, Floating a) => b -> a -> [b]

通过使用两个字面量,GHC 能够为每个字面量分配不同的类型以满足类型类约束。

编辑:这是@duplode建议的解决方案:

rndRoot :: (Integral a) => a -> a
rndRoot = round . sqrt . fromIntegral

我最初的建议 fromIntegral . round . sqrt 容易受到使用浮点运算所带来的所有常见问题的影响。

编辑:正如@WarrickMacmillan 指出的,oneToRndRoot 的签名也必须更改。以下是完整注释的整个工作程序。

rndRoot :: Integral a => a -> a
rndRoot = round . sqrt . fromIntegral

oneToRndRoot :: Integral a => a -> [a]
oneToRndRoot x = [1..rndRoot(x)]

modulo :: (Ord p, Num p) => p -> p -> p
modulo x y
  | n < 0 = x
  | otherwise = modulo n y
  where n = x - y

mapMod :: (Num b, Ord b) => b -> [b] -> [b]
mapMod x = map (modulo x)

mapModToRndRoot :: Integral a => a -> [a]
mapModToRndRoot n = mapMod n (oneToRndRoot n)

【讨论】:

  • 您的诊断正确,但有更好的解决方案:rndRoot = round . sqrt . fromIntegral。这样,OP 不需要更改其他函数的类型,也不需要从整数算术切换到浮点算术(可能带来的所有便利性和正确性问题 - 特别是 Haskell 特定的复杂性是floating-point ranges are generally a bad idea)。
  • @duplode 不错!已编辑。
  • 我只是让它推断出原始类型,然后将其添加到我的代码中,所以从现在开始我一定会指定它。对于其他任何人,oneToRndRoot 的类型签名也应该反映此更改oneToRndRoot :: (Integral a) =&gt; a -&gt; [a]oneToRndRoot x = [1..rndRoot(x)]
  • @WarrickMacmillan 谢谢。我将编辑我的答案。