7

这是我生命中第三次尝试学习 Haskell,这次是通过Learn you a Haskell ...。
当作者解释守卫时,他展示了这个例子:

bmiTell :: (RealFloat a) => a -> String 
bmiTell bmi  
| bmi <= 18.5 = "You're underweight, you emo, you!"  
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
| otherwise   = "You're a whale, congratulations!"

并说

这很容易让人想起命令式语言中的一个大 if else 树,只有这个更好,更易读。虽然大的 if else 树通常不受欢迎,但有时问题的定义如此离散,以至于您无法绕过它们。守卫是一个很好的选择。

我可以看到警卫更具可读性,但我不明白为什么该语法“好得多”
它更灵活?它更强大?后卫的最大优势是什么?

我的大问题可能是句子

虽然大的 if else 树通常不受欢迎,但有时问题的定义方式如此离散,以至于您无法绕过它们

谁能举个例子?

4

4 回答 4

12

Don 给出了使用守卫的主要动机,但除此之外,它们还与模式匹配很好地结合在一起。如果一个模式上的所有守卫都失败了,它就会进入下一个模式,因此您可以同时检查模式和条件,而不会出现大量重复的失败案例。这是一个(非常人为的)示例:

expandRange x (Just lo, Just hi) | hi < lo = (Just x, Just x)
expandRange x (Just lo, hi) | x < lo = (Just x, hi)
expandRange x (lo, Just hi) | x > hi = (lo, Just x)
expandRange _ range = range

如果我们认为Nothing是无界的,这需要一个元素进行比较,或者将负范围“扩展”到仅该元素,移动下/上限以包含该元素,或者如果该元素已经包含,则保持范围不变。

现在,考虑如何在不使用守卫的情况下编写上述内容!由于模式不同,您最终会复制多少次概念上相同的分支?是的,我意识到可以重写这个小例子来完全避免这个问题,但这并不总是可能的(或可取的)。

if在我看来,这种定义风格是您可以使用守卫表达的最重要的事情,尽管仍然可能,但如果将其编写为(无守卫的)模式案例和表达式的混合体,将会更加冗长且难以阅读.

于 2012-10-09T15:20:19.027 回答
7

守卫在语法上更轻:

  • 许多不同的案例
  • 嵌套案例

相比:

describeLetter c
   | c >= 'a' && c <= 'z' = "Lower case"
   | c >= 'A' && c <= 'Z' = "Upper case"
   | otherwise            = "Not an ASCII letter"

和:

describeLetter c =
    if c >= 'a' && c <= 'z'
        then "Lower case"
        else if c >= 'A' && c <= 'Z'
            then "Upper case"
            else "Not an ASCII letter"

规则部分在语法上更清晰,更易于维护。

此外,它们与视图模式很好地组合以产生令人愉悦的语法。

   f x | Just t <- bar x = Right (f t)
       | otherwise       = Left "some error case"

例如。

于 2012-10-09T14:54:20.430 回答
2

任何带有大量 if 的问题都可以被可视化为决策图。您引用的 LYAH 示例的决策图将如下所示:

                 .
                / \
               /   \
              /bmi? \
              \     /
               \   /
             /  \ /  \
            /  /   \  \
           /   |   |   \
          /    |   |    \
         /     |   |     \
        /      |   |      \
< 18.5 /18.5-25|   | 25-30 \ > 30
      /        |   |        \
    ...       ... ...       ...

守卫的一大优点是它们让语法结构反映了决策图的结构。如果您所拥有的只是if-then-else,那么您将不得不使用一系列嵌套的if 来实现上述决策图,即用级联的两个分支选择来编码多分支选择。嵌套 if 模糊了算法的高级思想。

现在,我认为 LYAH 的作者在你引用的那句话中得到的意思是,有时你不能比使用嵌套的 if-then-else 做得更好。但这只有在选择相互依赖时才是正确的,即当您的决策图包含许多菱形框(选择)时,每个菱形框(选择)只有两个分支,并且不能以任何其他方式重写。请注意,在bmiTell示例中,每个分支都相互独立,因为 BMI 只能属于 4 个类别,而这两个类别都不与任何其他类别重叠。

于 2012-10-09T15:12:47.913 回答
0

守卫只是在视觉上更明显并且不那么冗长⁄关于它们的“噪音”更少。

相比:

bmiTell bmi
 | bmi <= 18.5 = "................................."
 | bmi <= 25.0 = "..........................................."
 | bmi <= 30.0 = "...................................."
 | otherwise   = "................................"

bmiTell bmi =
 if       bmi <= 18.5 then "................................."
 else if  bmi <= 25.0 then "..........................................."
 else if  bmi <= 30.0 then "...................................."
 else                      "................................"

(另外,CA McCann对失败案例所说的话)。:)

于 2012-10-09T15:23:14.100 回答