Haskell自定义PatternSynonym为何任意顺序都能阻挡head空列表错误
首先我定义了如下模式同义词:
{-# Language PatternSynonyms #-} pattern x := y <- x @ y
该语法允许我对同一个参数同时执行多个模式匹配,常规的as绑定(@)不允许左侧为模式,但该模式同义词支持。
接下来我开启ViewPatterns扩展编写了如下测试函数:
{-# Language ViewPatterns #-} f ((_:_) := (head -> y)) = [ y ] f [] = []
这是复现问题的最小示例,函数接收一个参数,通过自定义模式同义词对其匹配两个模式:第一个匹配任意非空列表且不绑定变量,第二个对列表执行head函数,将结果绑定到y。
我最初的疑问是:head会不会触发空列表错误,还是另一个模式会阻止错误发生?
>>> f [] []
测试发现另一个模式成功阻止了错误。那我调换两个模式的顺序,是不是就会触发报错?
f' ((head -> y) := (_:_)) = [ y ] f' [] = []
>>> f' [] []
结果仍然没有报错。那是不是ViewPattern有自动检测错误、匹配失败时终止执行的智能逻辑?我又编写了不使用自定义模式的版本测试:
f'' (head -> y) = [ y ] f'' [] = []
>>> f'' [] *** Exception: Prelude.head: empty list
这个版本直接报错,说明上述假设不成立。难道GHC会优先匹配解构模式,再执行ViewPattern的函数逻辑?我把(_:_)替换成(reverse -> _:_),让解构前必须先执行函数,测试后行为没有变化,排除了该假设。
那会不会是惰性求值的原因?空列表匹配时变量没有被求值,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 [] []
此时undefined没有被求值,看起来模式匹配发生了短路。
我想确认该现象是否确实由惰性求值导致?GHC内部对应的运行逻辑是什么?
感谢Discord用户lexi在前期问题排查中提供的帮助。
内容的提问来源于stack exchange,提问作者Wheat Wizard

