如果我可以使用"方程式&#34 ;?为什么我应该使用case表达式?

时间:2013-12-24 18:03:23

标签: haskell case-expression

我正在从“Real World Haskell”一书中学习Haskell。在第66和67页中,它们使用此示例显示案例表达式:

fromMaybe defval wrapped =
    case wrapped of 
        Nothing    -> defval
        Just value -> value

我记得在F#中有类似的东西,但是(如本书前面所示)Haskell可以将函数定义为一系列方程;虽然AFAIK,F夏普不能。所以我试图用这样的方式来定义它:

fromMaybe2 defval Nothing = defval
fromMaybe2 defval (Just value) = value

我把它加载到GHCi中,经过几次结果后,我确信自己是相同的但是;这让我想知道,为什么在方程式时会出现case表达式:

  • 更容易理解(这是数学;为什么要使用case something of,谁说的?);
  • 不那么冗长(2对4行);
  • 需要更少的结构和合成糖(->可以是一个操作员,看看他们做了什么!);
  • 仅在需要时使用变量(在基本情况下,例如此wrapped只占用空间)。

案例表达有什么好处?它们的存在只是因为类似的基于FP的语言(如F#)有它们吗?我错过了什么吗?

编辑:

我从@ freyrs的回答中看到,编译器使这些完全相同。因此,方程式总是可以转换为案例表达式(如预期的那样)。我的下一个问题是反过来;可以使用编译器的相反路径并使用带有let / where表达式的方程来表达任何案例表达式吗?

6 个答案:

答案 0 :(得分:4)

这两个函数在Haskell(称为Core)中编译成完全相同的内部代码,您可以通过将标志-ddump-simpl -dsuppress-all传递给ghc来转储。

使用变量名称可能看起来有点令人生畏,但它实际上只是您在上面编写的代码的显式类型版本。唯一的区别是变量名称。

fromMaybe2
fromMaybe2 =
  \ @ t_aBC defval_aB6 ds_dCK ->
    case ds_dCK of _ {
      Nothing -> (defval_aB6) defval_aB6;
      Just value_aB8 -> (value_aB8) value_aB8
    }

fromMaybe
fromMaybe =
  \ @ t_aBJ defval_aB3 wrapped_aB4 ->
    case wrapped_aB4 of _ {
      Nothing -> (defval_aB3) defval_aB3;
      Just value_aB5 -> (value_aB5) value_aB5
    }

答案 1 :(得分:4)

这来自于拥有小型“内核”表达式语言的文化。 Haskell从Lisp的根源(即lambda calculuscombinatory logic)成长;它基本上是Lisp plus 语法加上显式数据类型定义加上模式匹配减去变异加上懒惰评估(懒惰评估本身首先在Lisp AFAIK中描述;即在70秒内)。

类似Lisp的语言是面向表达式的,即一切都是表达式,语言的语义作为一组约简规则给出,将更复杂的表达式转换为更简单的表达式,最终转化为“值”。

方程式不是表达式。几个方程式可以某种方式混合成一个表达式;你必须为此介绍一些语法; case就是那种语法。

Haskell的丰富语法被翻译成较小的“核心”语言,其case作为其基本构建块之一。 case必须是一个基本结构,因为Haskell中的模式匹配是语言的基本核心功能。


对于你的新问题,是的,你可以通过引入Luis Casillas显示in his answer的辅助功能,或者使用模式保护,所以他的例子变成了:

foo x y | (Meh o p) <- z = baz y p o
        | (Gah t q) <- z = quux x t q
    where 
       z = bar x

答案 2 :(得分:3)

论文"A History of Haskell: Being Lazy with Class"(PDF)对这个问题提供了一些有用的观点。第4.4节(“声明风格与表达风格”,第13页)是关于这个主题的。钱报价:

  

[W]参与了关于哪种风格“更好”的激烈辩论。一个潜在的假设是,如果可能的话,应该“只做一种方式”,以便例如同时拥有{{1}而let将是多余和令人困惑的。 [...]最后,我们放弃了潜在的假设,并为这两种风格提供了完整的语法支持。

基本上他们无法就一个人达成一致,所以他们两个都投入了。(请注意,引用明确是关于wherelet,但他们同时对待这个选择和where vs方程式选择作为同一基本选择的两种表现形式 - 他们称之为“宣言式”与“表达式”。)

在现代实践中,宣言风格(你的“系列方程式”)已经变得越来越普遍。在这种情况下经常可以看到case,您需要匹配从其中一个参数计算的值:

case

您可以随时重写此内容以使用辅助功能:

foo x y = case bar x of
            Meh o p -> baz y p o
            Gah t q -> quux x t q

这有一个非常小的缺点,你需要命名你的辅助功能 - 但在这种情况下foo x y = go (bar x) where go (Meh o p) = baz y p o go (Gah t q) = quux x t q 通常是一个非常好的名字。

答案 3 :(得分:2)

可以在期望表达式的任何地方使用案例表达式,而方程式则不能。例如:

1 + (case even 9 of True -> 2; _ -> 3)

你甚至可以嵌套case表达式,我已经看到了那样做的代码。但是我倾向于远离案例表达式,并试图用方程解决问题,即使我必须使用where/let引入局部函数。

答案 4 :(得分:1)

使用方程式的每个定义都等同于一个用例。例如

negate True = False
negate False = True

代表

negate x = case x of
             True -> False
             False -> True

也就是说,这是表达同一事物的两种方式,前者由GHC翻译成后者。

从我读过的Haskell代码中,尽可能使用第一种样式似乎是正常的。

the Haskell '98 report的第4.4.3.1节。

答案 5 :(得分:1)

你补充问题的答案是肯定的,但这很丑陋。

case exp of
  pat1 -> e1
  pat2 -> e2
  etc.

可以,我相信,

可以模仿
let
  f pat1 = e1
  f pat2 = e2
  etc.
in f exp

只要f在exp,e1,e2等中不是免费的,但你不应该这样做因为它太可怕了。