这个函数是否使用了haskell的惰性评估

时间:2014-09-15 16:26:28

标签: haskell functional-programming lazy-evaluation

我编写了以下函数来判断数字是否为素数。

isPrime :: Int -> Bool
isPrime n = and (map (\x -> (n `mod` x > 0))[2..(intSquareRoot n)])

intSquareRoot :: Int -> Int
intSquareRoot n = intSq n
  where
    intSq x
      | x*x > n = intSq (x - 1)
      | otherwise = x

我刚刚开始使用Haskell,所以这段代码可能对任何受过使用训练的人都是可怕的。但是,我很好奇这段代码是否使用了Haskell的懒惰评估。这部分

(map (\x -> (n `mod` x > 0))[2..(intSquareRoot n)])

将创建一个布尔列表,如果只有其中一个是假的(所以如果一个介于2和n的sqrt之间的数字除以n)那么使用'和'函数整个事件都是假的。但我认为将首先创建整个列表,然后使用'和'函数。这是真的?如果是这样,我怎样才能通过使用延迟求值来加快速度,以便函数停止并在找到n的第一个除数后返回false。在此先感谢您的帮助!

3 个答案:

答案 0 :(得分:5)

让我们看看mapand的定义,以便了解:

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs

and :: [Bool] -> Bool
and [] = True
and (x:xs) = x && and xs

我们还需要&&的定义:

(&&) :: Bool -> Bool -> Bool
True && x = x
_    && _ = False

请务必注意&&可能会短路,这意味着如果您通过False && undefined,则会立即获得False。计算True && undefined会抛出错误,因为必须检查第二个参数。

使用map,我们知道它很懒,因为:是懒惰的。我们可以生成f x,然后在需要时请求列表的其余部分。

所以看一下

and (map f [2..intSquareRoot n])
    where f x = n `mod` x > 0

这可以细分为(n = 19

and (map f [2..intSquareRoot n])
and (map f [2..4])
and (map f (2:[3..4]))
and (f 2 : map f [3..4])
f 2  && and (map f [3..4])
True && and (map f [3..4])
and (map f [3..4])
and (map f (3:[4..4]))
and (f 3 : map f [4..4])
f 3  && and (map f [4..4])
True && and (map f [4..4])
and (map f [4..4])
and (map f (4:[]))
and (f 4 : map f [])
f 4  && and (map f [])
True && and (map f [])
and (map f [])
and []
True

希望通过此扩展,您可以看到一次只处理列表中的一个元素,而列表的其余部分可以保持未计算直到需要。所有这些步骤都是通过直接从函数定义中替换来执行的。正如您可能看到的那样,如果我传入n = 27,则在计算f 3时,它会返回False并导致False && and (map f [4..5])返回{ {1}}不占用列表的其余部分。

答案 1 :(得分:3)

列表在Haskell中很懒,所以

[2.. anything] 

将构建一个未评估的thunk,直到您开始查看元素。 当您需要每个额外元素时,它将被评估。

and也是懒惰的,所以一旦你得到一个假结果,整个事情都会短路。

答案 2 :(得分:1)

如果你不相信理论上的论点,你也可以轻松地测试这个。尝试用一些大数字替换(intSquareRoot n),比如一百万。在测试8是否为素数时,算法的运行速度是否会慢一百万次,还是会立即停止?

如果它立即停止(实际上就是这种情况),它必须是懒惰的。当然,当你测试7时, 会慢一百万倍,因为7是素数,所以没有短路可做。