模式匹配比Haskell中的case表达式更优选的真实示例?

时间:2015-12-03 19:58:41

标签: haskell functional-programming pattern-matching

所以我一直忙于真实世界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或其他地方,哪一个能够看到哪种方法是首选的?

4 个答案:

答案 0 :(得分:15)

首先是一个简短的术语转移:我会称这两个"模式匹配"。我不确定是否有一个很好的术语来区分模式匹配 - 通过案例和模式匹配 - 通过多重定义。

两者之间的技术区别确实很轻。您可以通过要求GHC使用-ddump-simpl标志转储为两个函数生成的核心来自行验证。我在几个不同的优化级别尝试了这个,并且在所有情况下,核心的唯一差异是命名。 (顺便说一句,如果有人知道一个好的"语义差异计划的核心 - 它至少知道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子句附加到绑定站点,因此它们不能跨多个方程共享,但可以跨多个案例共享。例如:

-- 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表达式的模式是绑定站点,因此也可以在case表达式中进行模拟:

null x = case x of
  [] -> answer where answer = True
  x:xs -> answer where answer = False

where子句是附加到case的模式还是等式,取决于缩进,当然。

答案 1 :(得分:4)

如果我没记错的话,这两种情况都会发生在" desugar"在ghc中使用相同的核心代码,因此选择纯粹是风格。我个人会去第一个。正如有人所说,它更短,你所说的模式匹配"旨在以这种方式使用。 (实际上第二个版本也是模式匹配,只是使用不同的语法)。

答案 2 :(得分:1)

这是一种风格偏好。有些人有时会争辩说,一个或另一个选择会使某些代码更改花费更少的努力,但我通常会发现这样的论点,即使在准确的情况下,实际上也不会有很大的改进。你也愿意这样做。

值得一提的是Hudak,Hughes,Peyton Jones和Wadler的论文"A History of Haskell: Being Lazy With Class"。第4.4节是关于这个主题的。简短的故事:Haskell支持两者,因为设计师不能就一个人达成一致。是的,再次,这是一种风格偏好。

答案 3 :(得分:0)

当您在多个表达式上进行匹配时,case表达式开始变得更具吸引力。

f pat11 pat21 = ...
f pat11 pat22 = ...
f pat11 pat23 = ...
f pat12 pat24 = ...
f pat12 pat25 = ...

比写

更烦人
f pat11 y =
  case y of
    pat21 -> ...
    pat22 -> ...
    pat23 -> ...
f pat12 y =
  case y of
    pat24 -> ...
    pat25 -> ...

更重要的是,我发现在使用GADT时,"声明风格"似乎没有像我期望的那样从左到右传播证据。可能有一些技巧我没有解决,但我最终必须嵌套case表达式,以避免虚假的不完整模式警告。