文章目录

  • if-then-else
  • guard
  • 多路if
  • case-of
  • 小贴士:等号的误区

分支结构是编程中非常重要的语法结构,在Go语言中有if-elseswitch-case,Haskell中也有对应的if-then-elsecase-of。不同的是Haskell中的分支结构都是表达式,而不是语句。事实上,在Haskell中任何东西都是表达式。即便是向putStrLn这样的打印函数也是表达式,也就是说在Haskell中任何东西都必须是有值的。

if-then-else

我们以一个判断奇偶数的例子开始。

oddEeven a = if even a then "even"else "old"

习惯了其他语言的if-else,在面对Haskell的if-else-then时,会感觉很不习惯,因为它必须有一个值。在Go语言中,if语句表达的是如果xxx做什么,否则做什么。而在Haskell中的if表达式表达的是如果xxx是什么,否则是什么。

我们在来实现一个简单的计算函数,感受if-then-else的嵌套。

caculate a b c = if b == '+' then Just(a + c)else if b == '-' then Just(a - c)else if b == '*' then Just(a * c)else if b == '/' then if c == 0 then Nothingelse Just(a / c)else Nothing

guard

guard称为哨兵模式,它是对if语法的扩展,用来简化有许多if判断的情况下的代码编写。我们还是以上面的计算函数为例,首先让我们来看看Go语言对多if分支的优化。

func calculate(a, b float64, c byte) (float64, error) {switch {case c == '+':return a + b, nilcase c == '-':return a - b, nilcase c == '*':return a * b, nilcase c == '/':if b == 0 {return 0, fmt.Errorf("divide zero")} else {return a / b, nil}default:return 0, fmt.Errorf("operation not support")}
}

Haskell的哨兵模式和Go语言的switch有些相似,写法上要简洁许多。

caculate' a b c | b == '+' = Just (a + c)| b == '-' = Just (a - c)| b == '*' = Just (a * c)| b == '/' && c == 0 = Nothing | b == '/' = Just (a / c)| otherwise = Nothing

注意看哨兵模式和普通函数在写法上的区别,Bool表达式出现在参数和=之间,以|开始,语法模式变成了函数名 参数 | 条件 = 值。除了哨兵模式,在函数参数和=之间是不会有其他内容的。这也是一个经常让初学者感到困惑不已的地方。

哨兵模式最后的otherwise相当于Go里面的defaultotherwise虽然也不是必须的,但是如果没有你会收到一条警告,并且当出现无法匹配的条件时,函数会直接崩溃。显然,遗漏otherwise并不是一个程序员该有的素养。

在哨兵模式中,条件会从上到下依次匹配。所以我们将b == '/' && c == 0放到了b=='/'的前面,当然也可以不去判断除数是否为零,因为在Haskell中除0会得到Infinity而不是崩溃,只是这里我们希望除0的结果是Nothing

哨兵模式不能自我嵌套,从它的语法结构就决定了这一点。至于=后面,只要是表达式就行,比如我们可以用if表达式重写上面的代码。

caculate'' a b c | b == '+' = Just (a + c)| b == '-' = Just (a - c)| b == '*' = Just (a * c)| b == '/' = if c == 0 then Nothing else Just (a / c)| otherwise = Nothing

缩进对于Haskell来说非常重要,因为它会通过缩进来识别段落。但是无论编译器是否依赖于缩进,你都应该把你的代码对齐,因为这也是程序员的基本素养。

多路if

多路if英文名叫 MultiWayIf,这是Haskell的一个语言扩展,必须打开这个扩展才能支持多路if的语法。注意是语法级别的支持,开启的方式是在文件开头加上一行特殊的注释{-# LANGUAGE MultiWayIf #-}

{-# LANGUAGE MultiWayIf #-}caculate''' a b c = if | b == '+' -> Just (a + c)| b == '-' -> Just (a - c)| b == '*' -> Just (a * c)| b == '/' -> if c == 0 then Nothing else Just (a / c)| otherwise -> Nothing

MultiWayIf 要比else if简洁许多,看完简直想把else-if丢进垃圾桶。

case-of

case-of对于Go语言中的switch-case,不同的是,case-of也是表达式。

caculate'''' a b c = case b of '+' -> Just (a + c)'-' -> Just (a - c)'*' -> Just (a * c)'/' -> case c of 0 -> Nothing_ -> Just (a / c)_   -> Nothing

case-of可以无限嵌套,但是作为一个素质的程序员,还是要禁止套娃。case-of使用_来匹配任何模式,或者任意值。语法上,_匹配可以不放在最后,但是它后面的模式将永远无法被匹配求值。

现在来思考一个问题:case-of做的是模式匹配,还是等值判断,还是两者皆有?

这个问题我现在也不能确定,但我更倾向于只有模式匹配,虽然这听起来很诡异。


小贴士:等号的误区

在Go语言中,=用来赋值,可以随意使用。然而到了Haskell中,=变成了一个很神奇的东西。特别是对于初学者,会对=感到很困惑,经常会在使用=时出现语法错误。这里我们就来好好理解理解Haskell中的=

在Haskell中,=的含义是绑定,而绑定的右边必须是表达式。无论是定义函数还是定义变量,都是绑定,它们并无多大区别,你甚至可以认为绑定变量就是定义了一个无参函数。

问题的原因就在于你无法在表达式中绑定变量。=只是产生一个绑定,而绑定本身并不是表达式,因此你无法将一个绑定作为表达式在绑定到另一个变量。这在任何语言中都是一样的,比如在Go语言中也无法写出a := b := 1这样的代码。

产生这一认知误区的另一个原因是等价转换错误。比如我们在Go语言中定义如下函数:

func add (a int) int {return a + 1
}

翻译到Haskell中如下:

add a = a + 1

请注意,上面两段代码并不是等价表达。和上面Haskell代码等价的Go语言表达代码是下面这个:

var addFn = add

你同样无法在add后面添加变量绑定。

但是我们知道,在Haskell中定义函数时是可以绑定变量的,这就是let-inwhere

let-in本身就是一个表达式,注意let x = 1let x = 1 in x的区别。let-in会产生一个很小的命名空间,在let中绑定的变量只在in中可见,整个表达式的值等于in中表达式的值。

where用于在函数中绑定顶层变量,在整个函数中可见,除了变量,也能绑定函数,非常方便。注意where本身并不是表达式,它只是函数语法的一部分。

【Haskell】分支表达式相关推荐

  1. case when then else_啃食Oracle:条件分支表达式CASE

    啃食Oracle:条件分支表达式CASE CASE表达式是条件分支表达式,类似于if - elsif -else条件分支语句.常见用法是在select的表达式列表中使用. 以下图示来自于官方文档 上图 ...

  2. 【转】Go 语言教程(2)——表达式

    保留关键字 语言设计简练,只有 25 个保留关键字. break default func interface select case defer go map struct chan else go ...

  3. Kotlin入门与进阶:语法(二)类成员,运算符,语句,表达式

    上一篇文章着重介绍了Kotlin中的常量与变量,函数和Lambda表达式(点我补课),这一篇文章专注于类成员,运算符,各种语句和表达式. 类成员 包括属性和方法. 属性:也就是成员变量,是指类范围内的 ...

  4. 从0开始学习C语言————C语言简介,数据类型及分支语句

    C语言简介: C语言诞生于1970~1973年,丹尼斯.里奇和肯.汤普逊编写完成的,归属于美国的贝尔实验室 C语言专门为了编写操作系统而诞生的,因此天生适合对硬件编程,也非常适合数据结构和算法的实现, ...

  5. Microsoft Power Platform 基础到实战(3)-Power BI (1)-数据分析表达式 DAX(1)

    目录 概述 计算 度量值 计算列 计算表 行级安全性 查询 公式 在公式中使用多个函数 函数概述 聚合函数 日期和时间函数 筛选器函数 财务函数 信息函数 逻辑函数 数学和三角函数 其他函数 关系函数 ...

  6. Go核心开发学习笔记(九)—— 顺序控制,分支控制

    程序流程控制 决定程序如何执行,常用三大流程控制语句 顺序控制 分支控制:if-else 循环控制:for 符合条件前循环控制,符合条件后循环控制(笔记十去记录) 顺序控制 从上到下依次执行,每个程序 ...

  7. 【 Verilog HDL 】case, casez, casex 之干货总结

    这几天在做一个无人机定位的项目,时间比较紧,自己也不太懂,所以就边忙别愁就没有了精力写博客了.可是想想这样也不好,还是抽出点时间写博客,即使写的比较简单也行,至少能解答自己的疑惑就够了. Verilo ...

  8. Verilog中case,casex,casez的区别

    在case语句中,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配. Note: casez与casex语句是case语句的两种变体, 在写testbench时用到,属于不可综合的 ...

  9. Verilog学习----条件语句、循环语句、块语句与生成语句

    1.条件语句(if_else语句) 3钟形式的if语句: 1)if(表达式)语句.如 if(a>b) out1 = int1; 2)if(表达式) 语句: else 语句:如 if(a>b ...

最新文章

  1. Tensorflow多线程输入数据处理框架(一)——队列与多线程
  2. iOS开发之设计模式篇
  3. 架构师之路 — 数据库设计 — 关系型数据库的外键约束与关联
  4. java jvm性能调优_java jvm性能优化
  5. 服务提供者框架(Service Provider Framework)
  6. linux图形图像三剑客,就linux三剑客简单归纳
  7. vue商城项目开发:底部导航样式、顶部导航矩阵和轮播图
  8. 机器学习实战-逻辑回归-19
  9. mac命令行将输出写入文件_如何在Linux中使用命令行将PDF文件转换为可编辑文本...
  10. 百兆工业交换机与千兆工业交换机如何计算码率?
  11. Nginx_查看并发连接数
  12. 插入排序算法(C实现)
  13. 1811114每日一句
  14. BZOJ1070[SCOI2007] 修车
  15. 国外一些DICOM资源下载网址
  16. css实现烟雾效果(css制作汽车尾气排放效果)
  17. RPC框架简析--Pigeon
  18. 贪吃蛇python游戏
  19. 搜索关键词优化 助力全网霸屏营销
  20. 纹波(ripple)的定义

热门文章

  1. 心生热爱,所以只身前来 | Allan,很高兴再认识你
  2. AD20 快捷键设置
  3. python如何将日期字符串格式化年月日
  4. Jieba分词Python简单实现
  5. 田蕴章书法讲座《每日一题,每日一字》(1) 文字整理 ——永字八法
  6. latex如何打argmax
  7. Cookie 设置 expires(less than a day)
  8. GitHub的Windows客户端的使用教程
  9. 最新、最全、含金量最高的Java开发学习资料,爱学习的小伙伴们赶紧狂欢吧!
  10. 大数据告诉你劈腿的人有哪4种表现