我编写了以下函数来判断数字是否为素数。
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。在此先感谢您的帮助!
答案 0 :(得分:5)
让我们看看map
和and
的定义,以便了解:
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是素数,所以没有短路可做。