【Haskell】分支表达式
文章目录
- if-then-else
- guard
- 多路if
- case-of
- 小贴士:等号的误区
分支结构是编程中非常重要的语法结构,在Go语言中有if-else
和switch-case
,Haskell中也有对应的if-then-else
和case-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里面的default
,otherwise
虽然也不是必须的,但是如果没有你会收到一条警告,并且当出现无法匹配的条件时,函数会直接崩溃。显然,遗漏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-in
和where
。
let-in
本身就是一个表达式,注意let x = 1
和let x = 1 in x
的区别。let-in
会产生一个很小的命名空间,在let
中绑定的变量只在in
中可见,整个表达式的值等于in
中表达式的值。
where
用于在函数中绑定顶层变量,在整个函数中可见,除了变量,也能绑定函数,非常方便。注意where
本身并不是表达式,它只是函数语法的一部分。
【Haskell】分支表达式相关推荐
- case when then else_啃食Oracle:条件分支表达式CASE
啃食Oracle:条件分支表达式CASE CASE表达式是条件分支表达式,类似于if - elsif -else条件分支语句.常见用法是在select的表达式列表中使用. 以下图示来自于官方文档 上图 ...
- 【转】Go 语言教程(2)——表达式
保留关键字 语言设计简练,只有 25 个保留关键字. break default func interface select case defer go map struct chan else go ...
- Kotlin入门与进阶:语法(二)类成员,运算符,语句,表达式
上一篇文章着重介绍了Kotlin中的常量与变量,函数和Lambda表达式(点我补课),这一篇文章专注于类成员,运算符,各种语句和表达式. 类成员 包括属性和方法. 属性:也就是成员变量,是指类范围内的 ...
- 从0开始学习C语言————C语言简介,数据类型及分支语句
C语言简介: C语言诞生于1970~1973年,丹尼斯.里奇和肯.汤普逊编写完成的,归属于美国的贝尔实验室 C语言专门为了编写操作系统而诞生的,因此天生适合对硬件编程,也非常适合数据结构和算法的实现, ...
- Microsoft Power Platform 基础到实战(3)-Power BI (1)-数据分析表达式 DAX(1)
目录 概述 计算 度量值 计算列 计算表 行级安全性 查询 公式 在公式中使用多个函数 函数概述 聚合函数 日期和时间函数 筛选器函数 财务函数 信息函数 逻辑函数 数学和三角函数 其他函数 关系函数 ...
- Go核心开发学习笔记(九)—— 顺序控制,分支控制
程序流程控制 决定程序如何执行,常用三大流程控制语句 顺序控制 分支控制:if-else 循环控制:for 符合条件前循环控制,符合条件后循环控制(笔记十去记录) 顺序控制 从上到下依次执行,每个程序 ...
- 【 Verilog HDL 】case, casez, casex 之干货总结
这几天在做一个无人机定位的项目,时间比较紧,自己也不太懂,所以就边忙别愁就没有了精力写博客了.可是想想这样也不好,还是抽出点时间写博客,即使写的比较简单也行,至少能解答自己的疑惑就够了. Verilo ...
- Verilog中case,casex,casez的区别
在case语句中,敏感表达式中与各项值之间的比较是一种全等比较,每一位都相同才认为匹配. Note: casez与casex语句是case语句的两种变体, 在写testbench时用到,属于不可综合的 ...
- Verilog学习----条件语句、循环语句、块语句与生成语句
1.条件语句(if_else语句) 3钟形式的if语句: 1)if(表达式)语句.如 if(a>b) out1 = int1; 2)if(表达式) 语句: else 语句:如 if(a>b ...
最新文章
- Tensorflow多线程输入数据处理框架(一)——队列与多线程
- iOS开发之设计模式篇
- 架构师之路 — 数据库设计 — 关系型数据库的外键约束与关联
- java jvm性能调优_java jvm性能优化
- 服务提供者框架(Service Provider Framework)
- linux图形图像三剑客,就linux三剑客简单归纳
- vue商城项目开发:底部导航样式、顶部导航矩阵和轮播图
- 机器学习实战-逻辑回归-19
- mac命令行将输出写入文件_如何在Linux中使用命令行将PDF文件转换为可编辑文本...
- 百兆工业交换机与千兆工业交换机如何计算码率?
- Nginx_查看并发连接数
- 插入排序算法(C实现)
- 1811114每日一句
- BZOJ1070[SCOI2007] 修车
- 国外一些DICOM资源下载网址
- css实现烟雾效果(css制作汽车尾气排放效果)
- RPC框架简析--Pigeon
- 贪吃蛇python游戏
- 搜索关键词优化 助力全网霸屏营销
- 纹波(ripple)的定义