Haskell 在哪种意义上,警卫比命令式警卫更好?(哈斯克尔新成员)

Haskell 在哪种意义上,警卫比命令式警卫更好?(哈斯克尔新成员),haskell,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 <= 3

这是我一生中第三次尝试学习哈斯克尔,这次是通过。
当作者解释警卫时,他展示了这个例子:

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!"
bmiTell::(RealFloat a)=>a->String
体重指数

|bmi防护装置在语法上较轻,适用于:

  • 许多不同的情况
  • 嵌套案例
比较:

describeLetter c
   | c >= 'a' && c <= 'z' = "Lower case"
   | c >= 'A' && c <= 'Z' = "Upper case"
   | otherwise            = "Not an ASCII letter"
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                      "................................"
描述符c

|c>='a'&&c='a'&&c='a'&&c='a'&&c如果周围有很多if,任何问题都可以被形象化为决策图。您引用的LYAH示例的决策图如下:

                 .
                / \
               /   \
              /bmi? \
              \     /
               \   /
             /  \ /  \
            /  /   \  \
           /   |   |   \
          /    |   |    \
         /     |   |     \
        /      |   |      \
< 18.5 /18.5-25|   | 25-30 \ > 30
      /        |   |        \
    ...       ... ...       ...
。
/ \
/   \
/体重指数\
\     /
\   /
/  \ /  \
/  /   \  \
/   |   |   \
/    |   |    \
/     |   |     \
/      |   |      \
< 18.5 /18.5-25|   | 25-30 \ > 30
/        |   |        \
...       ... ...       ...
guards的最大优点是,它们让语法结构反映决策图的结构。如果您只有If-then-else,那么您必须使用一系列嵌套的If来实现上述决策图,即用两个分支选择的级联来编码多分支选择。嵌套的if会模糊算法的高层思想


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

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

expandRange x(刚好低,刚好高)| hihi=(lo,Just x)
expandRange uRange=范围
如果我们认为
Nothing
是无界的,则需要对元素进行比较,或者将负范围“扩展”到仅该元素,移动下限/上限以包含该元素,或者如果元素已包含,则保持范围不变

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


在我看来,这种定义风格是你可以用防护来表达的最重要的东西,尽管仍然可能,但如果它是混合(无防护)的话,它会更加冗长,更难阅读模式案例和
if
表达式。

防护装置在视觉上更加明显,不太冗长⁄其“噪音”更少

比较:

describeLetter c
   | c >= 'a' && c <= 'z' = "Lower case"
   | c >= 'A' && c <= 'Z' = "Upper case"
   | otherwise            = "Not an ASCII letter"
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                      "................................"
bmi

|bmi除了更具可读性之外,并没有添加任何内容。@KarolisJuodelė:他们确实添加了一些东西:图案保护,这不能用简单的
if
那么
else
–也不能用
case
,但只能用两者的组合。尽管增加缩进是不必要的。您可以将其垂直对齐。
Right(ft)
中的
f
是否正确?看起来你会有一个无限递归类型。但请注意:这不是惯用的Haskell。@DonStewart,我认为这是主观的。有时你需要一个函数中的一个构造,你不想定义一个单独的函数,这样你就可以用警卫把它写下来。在这种情况下的另一个选项是GHC 7.6中新增的一个。所以我学到了两件事:在大多数情况下,如果是的话,警卫的可读性更高,但当你结合模式匹配时,他们真正的力量就会显现出来。哈斯凯尔对我来说就像一块知识块:你要么把它全部拼凑起来,要么就是不懂。@PabloGrisafi:而且,“更具可读性”一点也不可掉以轻心!同时简洁、可读和富有表现力是高级语言的价值所在——这三者都是绝对必要的。没错,可读性非常重要。我想说的是,除了您展示的模式匹配组合之外,警卫的概念与命令式语言中的if/else几乎相同。哈斯克尔对我的命令式思维方式来说真的很不一样,也很奇怪,但警卫毕竟不是那么陌生。@PabloGrisafi:是的。模式匹配也是如此——两者都不是固有的功能(在某些方面,它们是相反的)。事实上,我有时希望命令式语言包括模式匹配(在代数数据类型上)和保护——它们类似于典型的
开关
大小写
结构,但更有用。
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                      "................................"