为了开始这整个事情,我正在使用定义如下的模式同义词:
{-# Language PatternSynonyms #-}
pattern x := y <- x @ y
这允许我一次跨一个参数运行多个模式匹配。常规 as binding ( @
) 不允许左侧成为模式,但确实如此。
有了这个,我做了以下玩具功能
{-# Language ViewPatterns #-}
f ((_:_) := (head -> y)) =
[ y ]
f [] =
[]
我敢肯定,这不是实现这一点的最佳方式,但它是所讨论行为的最低工作示例。
这有一个接受单个参数的函数。它使用定义的同义词将参数与两个模式匹配。第一个模式匹配任何非空列表并且不进行绑定。第二个在列表上运行 head 函数并绑定y
到结果。
所以问题是会head
导致错误还是其他模式会阻止它?
>>> f []
[]
其他模式阻止它!好吧,如果我按照其他顺序执行它们,那么它应该会中断吗?
f' ((head -> y) := (_:_)) =
[ y ]
f' [] =
[]
>>> f' []
[]
没有!它仍然有效。所以现在我的问题是:第二种模式有什么作用吗?也许视图模式具有某种智能行为,它调用函数并在发生错误时使模式失败......
f'' (head -> y) =
[ y ]
f'' [] =
[]
>>> f'' []
[*** Exception: Prelude.head: empty list
不……它没有。这失败了。(_:_)
无论错误在哪一边,都会以某种方式阻止错误。也许 ghc 更喜欢在视图模式之前匹配解构模式?为了测试这一点,我可以(_:_)
用(reverse -> _:_)
. 这样,它必须先运行一个函数,然后才能进行解构。
但是经过测试,新模式不会改变行为。可以排除这个假设。
所以也许是懒惰? x
如果列表为空,则无法评估,因此它位于 thunk 中并且永远不会发生错误。似乎有些情况。如果我替换(head -> x)
为(undefined -> x)
我们的行为没有变化。
但是,如果我将其替换为(undefined -> "yo")
:
f ((undefined -> "yo") := (x:_)) = [x]
f [] = []
>>> f []
*** Exception: Prelude.undefined
undefined
确实得到评估。这似乎表明该模式迫使评估与"yo"
. 如果我现在切换顺序:
f ((x:_) := (undefined -> "yo")) = [x]
f [] = []
>>> f []
[]
它不被评估。看来现在我们正在短路模式匹配。
所以懒惰假设似乎有道理?它对我来说仍然非常不透明,我希望有人对 ghc 的内部有更多经验来证实这个假设。
所以我的问题是现在发生了什么?是懒惰吗?它是如何工作的?
非常感谢不和谐用户 lexi。到目前为止,他们对诊断有很大帮助。