3

我正在从“Real World Haskell”一书中学习 Haskell。在第 66 和 67 页中,他们用这个例子展示了 case 表达式:

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

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

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

我将它加载到 GHCi 中,经过几个结果后,我确信它是一样的但是;这让我想知道,为什么在等式时应该有 case 表达式:

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

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

编辑:

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

4

6 回答 6

4

这来自一种拥有小型“内核”面向表达的语言的文化。Haskell 源自 Lisp 的根源(即lambda 演算组合逻辑);它基本上是 Lisp加上语法加上显式数据类型定义加上模式匹配减去突变加上惰性评估(惰性评估本身首先在 Lisp AFAIK 中描述;即在 70 年代)。

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

方程不是表达式。几个方程可以以某种方式组合成一个表达式;您必须为此引入一些语法;case是那个语法。

Haskell 的丰富语法被翻译成更小的“核心”语言,这case是它的基本构建块之一。case必须是一个基本的结构,因为 Haskell 中的模式匹配是该语言的一个基本的核心特性。


对于您的新问题,是的,您可以通过 Luis Casillas在他的答案中展示的那样引入辅助功能,或者使用模式防护,因此他的示例变为:

foo x y | (Meh o p) <- z = baz y p o
        | (Gah t q) <- z = quux x t q
    where 
       z = bar x
于 2013-12-24T18:53:29.233 回答
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
    }
于 2013-12-24T18:47:35.477 回答
3

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

[W] 就哪种风格“更好”展开了激烈的辩论。一个潜在的假设是,如果可能的话,应该“只有一种方法来做某事”,因此,例如,同时拥有letwhere将是多余和混乱的。[...] 最后,我们放弃了基本假设,并为这两种风格提供了完整的句法支持。

基本上他们不能就其中一个达成一致,所以他们都加入了。(请注意,引用明确地是关于letand where,但他们将那个选择和casevs. equations 选择视为相同基本选择的两种表现形式——他们称之为“声明风格” " 与 "表达方式")

在现代实践中,声明风格(您的“一系列方程式”)已成为更常见的风格。 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

这有一个非常小的缺点,您需要命名您的辅助函数 - 但go在这种情况下通常是一个非常好的名称。

于 2013-12-24T23:37:04.007 回答
2

案例表达式可以在任何需要表达式的地方使用,而方程式则不能。例子:

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

您甚至可以嵌套 case 表达式,我已经看到了这样做的代码。然而,我倾向于远离 case 表达式,并尝试用方程解决问题,即使我必须使用where/let.

于 2013-12-24T18:26:38.467 回答
1

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

negate True = False
negate False = True

代表

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

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

从我读过的 Haskell 代码来看,尽可能使用第一种样式似乎是规范的。

请参阅Haskell '98 报告的第 4.4.3.1 节。

于 2013-12-24T18:22:33.690 回答
1

您添加的问题的答案是肯定的,但它非常难看。

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

我相信,可以效仿

let
  f pat1 = e1
  f pat2 = e2
  etc.
in f exp

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

于 2013-12-24T19:54:03.010 回答