我应该在哪里以及为什么要使用额外的空模式?

Where and Why should I use extra empty patterns?

例如:

intersectBy : (a -> a -> Bool) -> List a -> List a -> List a
intersectBy _  [] _     =  []
intersectBy _  _  []    =  []
intersectBy eq xs ys    =  [x | x <- xs, any (eq x) ys]

[] 有额外的模式,似乎在 Haskell Data.List 中使用了它们,但那是什么优化?这里与 Idris 有什么不同?

我问是因为我听说 "it will make reasoning about it more difficult" 和说我的人没有时间完全解释它。

做"reduce the proof"的功能我都怀疑能不能看懂

有人可以从 Haskell 和 Idris 的立场向我解释额外模式的政治,以便我能够理解并看到差异。

从语义上讲,模式

intersectBy _  [] _     =  []

看起来多余,即使从性能的角度来看也是如此。相反,在 Haskell

intersectBy _  _  []    =  []

不是多余的,否则

intersectBy (==) [0..] []

会发散,因为理解会尝试尝试所有元素 x <- [0..]

不过,我不确定我是否喜欢这种特殊情况。为什么我们不应该添加一个涵盖 intersectBy (==) [0..] [2] 的特例,使其 returns [2]?此外,如果性能是问题,在许多情况下我想通过预排序使用 O(n log n) 方法,即使这不适用于无限列表并且需要 Ord a.

@chi 解释了 _ _ [] 的情况,但是 _ [] _ 也有一个目的:它规定了如何 intersectBy handles bottom。定义如下:

λ. intersectBy undefined    []     undefined
[]
λ. intersectBy   (==)    undefined    []
*** Exception: Prelude.undefined

删除第一个模式,它变成:

λ. intersectBy undefined undefined    []
[]
λ. intersectBy   (==)       []     undefined
*** Exception: Prelude.undefined

我不是 100% 确定这一点,但我相信在第一个模式中不 绑定 任何东西也有性能优势。最终模式将在没有 评估 eqys 的情况下为 xs == [] 给出相同的结果,但 AFAIK 它仍然是 allocates stack space 他们的 thunks .

无需猜测何时可以通过 git blame、GHC Trac 和图书馆邮件列表查找历史记录。

原来定义只是第三个方程,

intersectBy eq xs ys    =  [x | x <- xs, any (eq x) ys]

https://github.com/ghc/ghc/commit/8e8128252ee5d91a73526479f01ace8ba2537667中添加了第二个方程作为strictness/performance改进,同时添加了第一个方程以使新定义始终至少与定义相同原来的。否则,intersectBy f [] _|_ 将是 _|_,而它之前是 []

在我看来,当前的定义现在是最懒惰的:它对任何输入都是尽可能定义的,除了必须选择是先检查左列表还是右列表是否为空。 (而且,正如我上面提到的,这个选择是为了与历史定义一致。)

在 Idris 中 有一个很大的不同:Idris 列表总是有限的!此外,Idris 是一种非常严格的(按值调用)语言,并且可以选择使用完整性检查器,因此可以合理地假设参数列表中不会隐藏任何底部。这种差异的重要性在于,这两个定义在 Idris 中比在 Haskell 中在语义上更接近相同。选择使用哪个可以基于证明函数属性的难易程度,或者可以基于简单性。