【问题标题】:Haskell - What does the lambda in this primality test mean and how does this work?Haskell - 这个素数测试中的 lambda 是什么意思,它是如何工作的?
【发布时间】:2013-10-29 13:19:14
【问题描述】:

来自http://www.haskell.org/haskellwiki/Testing_primality,有这段代码:

isPrime n = n > 1 &&
    foldr (\p r -> p*p > n || ((n `rem` p) /= 0 && r)) True primes

其中 primes 是一个素数列表(可能是无限的)。

两个问题:

  1. 如何读取传递给 foldr 函数的 lambda
  2. 既然foldr从右边开始,为什么这个函数在传递一个无限的素数列表时会起作用?我猜 lambda 中存在短路?

【问题讨论】:

标签: haskell primes lambda primality-test


【解决方案1】:

褶皱可能很容易在视觉上掌握:

   foldr g z [a,b,c...,x] === g a (g b (g c (g ... (g x z) ... )))

所以如果g p r 不使用它的第二个参数,它不会强制任何进一步的列表访问。就像你说的,因为||(“逻辑或”)的短路行为。

还有foldl g z [a,b,c...,x] === (g ... (g (g (g z a) b) c) ... x)

要阅读该函数,首先我们注意到折叠应用于素数列表[2,3,5,7,11,13,17..]。所以可以读作

isPrime n = n > 1 &&
   foldr (\p r -> p*p > n || ((n `rem` p) /= 0 && r)) True primes
===
isPrime n = n > 1 &&
   (2*2 > n || (rem n 2 /= 0 &&
   (3*3 > n || (rem n 3 /= 0 &&
   (5*5 > n || (rem n 5 /= 0 && 
   -- the expansion stops when p*p > n
   -- because (True || r) === True
   ......................... && 
   (True) ... ))))))

foldr的组合函数中,第二个参数是折叠列表其余部分的“结果”; r 是一个暗示性的名称。

【讨论】:

  • 我接受了这个答案,因为它更清楚地解释了折叠和 lamdba 在这个函数中的工作方式。谢谢!
【解决方案2】:

惰性求值意味着布尔短路逻辑会停止正在求值的函数链,即使逻辑在函数内部也是如此。

举个简单的例子,对于任何可折叠的数据类型,您都可以像这样编写null 函数:

null t = foldr (\x b -> False && b) True t

这个函数永远不会被多次调用,因为对于一个具有多个元素的实例,它会计算为

False && *thunk* foldr...

短路布尔值 and 意味着永远不会评估 thunk,因此这将很高兴地适用于无限结构。这就是为什么你不应该实现null 来检查size == 0

这不适用于严格的语言; foldr 的每次迭代都将被依次评估并传递给下一个。

至于 lambda...

isPrime n = n > 1 &&
    foldr (\p r -> p*p > n || ((n `rem` p) /= 0 && r)) True primes

可以这样写:

isPrime n = n > 1 &&
    foldr f True primes
    where
        f p r = p*p > n || ((n `rem` p) /= 0 && r)

希望对您有所帮助。

编辑:如果不清楚,该函数中的短路布尔值 || 的工作方式与上面更简单的示例相同。

【讨论】:

  • 这是对折叠的一个很好的解释,但我认为我接受的答案更好地说明了折叠和 lambda 在这种情况下是如何工作的。
  • 好吧,我一开始说它是关于短路布尔逻辑的,在那个 lambda 中间有一个很大的 || 耳光;)
  • 在您更新之前,您根本不清楚您所说的“lambda 是如何工作的?”是什么意思。已更新。
【解决方案3】:

lambda 部分看起来像这样被翻译成 pythonish 伪代码:

def f(p, r):
  # invariant: r is True iff we have not seen a divisor of n yet.
  # we must return an updated r

  if p > sqrt(n): 
     return True # because we need a divisor less than that

  if p does not divide n and r is True:
     return True # because p is not a divisor neither we have seen one yet

  return False  # and this will become a new r and will return False forever.

至于foldr 部分,这确实很棘手。列表中的foldrfirst evaluates the result for the tail (r) 然后将该函数应用于头部和 r。所以一定有一点可以不用进一步计算尾巴。

当头部至少为sqrt(n) 时会发生这种情况,这意味着:如果有一个除数,你可以找到它,但现在返回 True 并且不计算尾部。

【讨论】:

  • 我更喜欢if p does not divide n: return r。 -- 关于foldr:Haskell 是惰性的,所以实际上r 在第一次调用组合函数之前没有计算。只有在请求 r 的值时,才会尝试计算 r 的值。
猜你喜欢
  • 2022-11-27
  • 2012-08-21
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多