【问题标题】:In Haskell, are guards or matchers preferable?在 Haskell 中,守卫或匹配器更可取吗?
【发布时间】:2014-09-19 23:25:46
【问题描述】:

我正在学习 Haskell,但我并不总是清楚何时使用匹配器以及何时使用守卫。对于某些场景,似乎可以使用匹配器和守卫来实现基本相同的目的。是否有一些规则或启发式方法可以更好地使用匹配而不是守卫,反之亦然?一个比另一个性能更好吗?

为了说明我的意思,下面是我编造的几个愚蠢的例子,它们似乎是等价的,但一个版本使用匹配器,另一个使用守卫:

listcheck :: [a] -> String
listcheck [] = "List is null :-("
listcheck a = "List is NOT null!!"

listcheck' a
    | null a = "List is null :-("
    | otherwise = "List is NOT null!!"

luckyseven :: Int -> String
luckyseven 7 = "SO LUCKY!"
luckyseven b = "Not so lucky :-/"

luckyseven' c
    | c == 7 = "SO LUCKY!"
luckyseven' c = "Not so lucky :-/"

谢谢!

【问题讨论】:

  • 我会尽可能使用模式匹配,必要时使用守卫。

标签: haskell functional-programming


【解决方案1】:

这些通常可以互换使用,但两者之间存在显着差异。模式匹配只能发生在构造函数上,因此不能在模式内部执行计算,而守卫只是多分支 if-else 语句。例如,我无法编写与以下内容等效的模式:

func :: Int -> Int
func x
    | even x = 3 * x
    | odd x  = 7 * x        -- alternatively "otherwise = 7 * x" to get rid of all those pesky compiler warnings

仅使用模式匹配是不可能的。你也不能这样做

func :: Int -> Maybe String
func x
    | x < 0     = Nothing
    | x == 0    = Just "Zero"
    | x < 20    = Just "Small"
    | x < 100   = Just "Big"
    | x < 1000  = Just "Huge"
    | otherwise = Just "How did you count that high?"

相反,如果没有辅助函数,使用 ADT 的守卫不会为您提供太多信息。如果我有类型

data Expr
    = Literal Int
    | Add  Expr Expr
    | Mult Expr Expr
    | Negate Expr
    deriving (Eq, Show)

使用守卫来写等价的

eval :: Expr -> Int
eval (Literal i)  = i
eval (Add  e1 e2) = eval e1 + eval e2
eval (Mult e1 e2) = eval e1 * eval e2
eval (Negate e)   = negate (eval e)

会更加冗长、困难和烦人。事实上,在某种程度上,你不得不求助于模式匹配来做类似的事情

getLiteral :: Expr -> Int
getLiteral (Literal i) = i
getLiteral _           = error "Not a literal"

其中引入了可以error的函数,这很糟糕。在这种情况下,使用模式匹配比使用守卫更可取。

【讨论】:

  • 第一个示例可能会给出一个非详尽的模式警告,因为您的 Haskell 编译器不太可能知道forall x. even x || odd x ~= True
  • @BoydStephenSmithJr。这是真的,但我是为了清楚起见,不一定要避免编译器可能给我的任何警告。我曾与自己争论过是否将其保留为otherwise = ...,或者对其发表评论,但最终决定保持原样。
  • 只是指出它是(某种)另一个区别。我同意它不会显着减损答案,这就是我投票的原因。
  • 请注意,功能更强大的语言有时可以通过模式匹配完成更多工作。例如,在 Coq 中,您可以编写 forall x, even x \/ odd x 的证明(使用适当的 evenodd 的归纳定义),然后对应用于您的变量的证明结构进行模式匹配。
  • 在第一个示例中,如果结果是奇数,则必须同时执行 even xodd x,这显然是多余的。在这种情况下,性能损失可以忽略不计,但通常情况并非如此。所以在这里使用| otherwise = ... 不仅是一种风格选择,而且客观上会提供更好的性能。
【解决方案2】:

对于您的特定示例,我会使用模式匹配,但会尽可能使用 _:

listCheck :: [a] -> String
listCheck [] = "List is null :-("
listCheck _  = "List is NOT null!!"

luckySeven :: Int -> String
luckySeven 7 = "SO LUCKY!"
luckySeven _ = "Not so lucky :-/"

这强调如果列表不为空,或者 Int 不为 7,则其他都不重要,并且您不会使用它的特定值来生成函数结果。 bheklilr 有能力地指出了其中一种选择绝对更可取的地方。

【讨论】:

    猜你喜欢
    • 2019-03-01
    • 1970-01-01
    • 2015-08-15
    • 1970-01-01
    • 2020-05-03
    • 2021-04-04
    • 2017-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多