因此,我一直忙于真实世界Haskell 书籍,并进行了lastButOne练习。我想出了2种解决方案,一种具有模式匹配
lastButOne :: [a] -> a
lastButOne ([]) = error "Empty List"
lastButOne (x:[]) = error "Only one element"
lastButOne (x:[x2]) = x
lastButOne (x:xs) = lastButOne xs
还有一个使用案例表达
lastButOneCase :: [a] -> a
lastButOneCase x =
case x of
[] -> error "Empty List"
(x:[]) -> error "Only One Element"
(x:[x2]) -> x
(x:xs) -> lastButOneCase xs
我想发现的是,什么时候模式匹配比大小写表达式更受青睐,反之亦然。这个示例对我来说还不够好,因为虽然两个功能都按预期工作,但它并没有导致我选择一个实现而不是另一个实现。因此,乍一看,选择“似乎”优先吗?
那么,在Haskell自己的源代码中还是在github或其他地方,是否有通过源代码的好案例,人们可以看到何时首选哪种方法?
最佳答案
首先是一个简短的术语转移:我将这两个都称为“模式匹配”。我不确定是否存在区分案例的模式匹配和案例定义的模式匹配的好术语。
两者之间的技术区别确实很轻。您可以使用-ddump-simpl
标志,要求GHC转储它为两个函数生成的核心,以进行验证。我在几个不同的优化级别上进行了尝试,在所有情况下,Core的唯一区别就是命名。 (顺便说一句,如果有人知道Core的一个好的“语义差异”程序-至少至少知道alpha等效性-我对此很感兴趣!)
但是,有一些小陷阱需要提防。您可能想知道以下各项是否也等效:
{-# LANGUAGE LambdaCase #-}
lastButOne = \case
[] -> error "Empty List"
(x:[]) -> error "Only One Element"
(x:[x2]) -> x
(x:xs) -> lastButOneCase xs
在这种情况下,答案是肯定的。但是考虑一下这个看起来相似的东西:
-- ambiguous type error
sort = \case
[] -> []
x:xs -> insert x (sort xs)
突然之间,这是一个类型类多态CAF,因此,在旧的GHC上,这将触发单态限制并导致错误,而带有显式参数的表面相同的版本则不会:
-- this is fine!
sort [] = []
sort (x:xs) = insert x (sort xs)
另一个较小的区别(我忘记了-谢谢Thomas DuBuisson提醒我)是在处理where子句。由于where子句附加到绑定(bind)位置,因此它们不能在多个方程式之间共享,而可以在多个案例之间共享。例如:
-- error; the where clause attaches to the second equation, so
-- empty is not in scope in the first equation
null [] = empty
null (x:xs) = nonempty
where empty = True
nonempty = False
-- ok; the where clause attaches to the equation, so both empty
-- and nonempty are in scope for the entire case expression
null x = case x of
[] -> empty
x:xs -> nonempty
where
empty = True
nonempty = False
您可能会认为这意味着您可以使用事例表达式无法做到的方程式做一些事,即,两个方程式中相同名称的含义不同,如下所示:
null [] = answer where answer = True
null (x:xs) = answer where answer = False
但是,由于
case
表达式的模式是绑定(bind)站点,因此也可以在case
表达式中进行模拟:null x = case x of
[] -> answer where answer = True
x:xs -> answer where answer = False
当然,
where
子句是附加到case
的模式还是附加到方程式,当然取决于缩进。