【问题标题】:Does this function make use of haskell's lazy evaluation这个函数是否利用了haskell的惰性求值
【发布时间】:2014-11-09 05:56:06
【问题描述】:

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

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)])

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

【问题讨论】:

  • 我会在map之前使用$而不是括号;这真是一个不错的工具:)
  • 你可以用any代替andany (\x -> n mod` x > 0) [2..intSquareRoot n]. any`相当于:any f = and . map f
  • @Bakuriu 其实是any f = or . map f。你想的是all f = and . map f
  • intSquareRoot 对于任何类型的大型输入都会难以置信缓慢。相反,您可能需要考虑基于旧的除平均技巧的循环。

标签: haskell functional-programming lazy-evaluation


【解决方案1】:

我们看看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]) 仅返回False 而不会消耗列表的其余部分。

【讨论】:

  • @Slugger 仅供参考,这是 等式推理 的一个例子,Haskellers 喜欢使用这个术语。这意味着我们应该能够用它的定义替换对函数的调用,因为它们是相等的,然后推理它的含义。在这里,我们将函数mapand&& 替换为它们的定义,并且能够推断出这个算法是惰性的。
【解决方案2】:

列表在 Haskell 中是惰性的,所以

[2.. anything] 

将构造一个未评估的 thunk,直到您开始查看元素。 当您要求每个附加元素时,都会对其进行评估。

and 也很懒惰,所以一旦你得到一个 False 结果,整个事情就会短路。

【讨论】:

  • 啊,这太棒了!非常感谢
【解决方案3】:

如果您不相信理论论证,您也可以轻松地对此进行测试。尝试用一些大数字替换(intSquareRoot n),比如一百万。在测试 8 是否为素数时,算法的运行速度是否慢了一百万倍,还是立即停止?

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-13
    • 2023-04-05
    • 1970-01-01
    • 2012-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多