我正在从“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
,谁说的?); ->
可以是一个操作员,看看他们做了什么!); wrapped
只占用空间)。案例表达有什么好处?它们的存在只是因为类似的基于FP的语言(如F#)有它们吗?我错过了什么吗?
我从@ freyrs的回答中看到,编译器使这些完全相同。因此,方程式总是可以转换为案例表达式(如预期的那样)。我的下一个问题是反过来;可以使用编译器的相反路径并使用带有let
/ where
表达式的方程来表达任何案例表达式吗?
答案 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 calculus和combinatory 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
将是多余和令人困惑的。 [...]最后,我们放弃了潜在的假设,并为这两种风格提供了完整的语法支持。
基本上他们无法就一个人达成一致,所以他们两个都投入了。(请注意,引用明确是关于where
和let
,但他们同时对待这个选择和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等中不是免费的,但你不应该这样做因为它太可怕了。