类型_Haskell笔记3

一.内置类型

几种常见的类型如下:

:有界整数,32位机器上的界限是

:无界整数,内置的大数类型,效率不如高

:单精度浮点数,6位小数

:双精度浮点数,15位小数

:布尔值,值为

:字符

:元组本身也是类型,只有一个值

内置的无界整数让大数运算变得非常方便,例如求的阶乘:

二.变量类型

读作“类型为”(),告诉编译器变量是类型(即类型)的

另外,类型的首字母都是大写的

P.S.虽然理论上很多场景不需要手动注明类型(编译器会自己推断),但实践建议是至少给顶层变量/函数都标明类型,当然,全都标上肯定是好习惯,毕竟明确的类型能够大大增强可读性,具体见Type signatures as good style

P.S.可以通过命令列出指定模块所有的类型推断,便于给现有代码补充类型

三.函数类型

一些常用函数的类型签名如下:

其中,到之间的部分是类型约束(声明类型变量),之后的部分是其类型。类型声明中的小写字母(例如)叫做类型变量,未加限定的类型变量(如类型中的)相当于泛型,用到类型变量的函数称之为多态函数

比如的含义是的类型是一个接受类型参数,返回的函数。表示的类型是接受两个类型参数,返回的(柯里化)函数。而表示的类型是接受两个List参数,返回另一个List的函数,这里的没有限定类型,所以List里的元素可以是任意类型

类型部分的读作“映射到”(),如何理解?

函数的数学定义是定义域到值域的映射关系,所以对应的数学含义是,也就是说映射到(的映射关系)就是,输入返回对应的

所以表示一个输入,返回函数的函数,继续调用这个返回函数,输入返回对应的。忽略柯里化特性的话,可以简单理解为接受两个参数,返回

四.Typeclass

其中,被称为typeclass,相当于interface,即定义了该类型成员必须具有的行为

除函数外的所有类型都属于,都可以判断相等性。另一些常见的typeclass如下:

:可以比较大小(能够通过等函数来比较大小,所以一定属于)

:可用字符串表示(除函数外,都是可Show的)。可以通过函数把其它类型转字符串

:与Show相反。可以通过函数把字符串转到其它类型

:可枚举,即连续的。包括,,,,,,和,这些类型都可以用于Range,可以通过和函数访问该类型值的后继和前驱

:有明确的上下界。可以通过和取指定类型的上下界(如)

:数值。成员都具有数值的特征

:整数。包括和

:小数。包括和

数字转换的话,大范围转小范围能够隐式完成(如转),小转大则需要通过之类的函数来完成,常见的场景是函数:

因为,而与无法直接相加,所以需要这样做:

另外,函数也很有意思,例如:

会根据上下文推断出目标类型,所以如果没有上下文就无法推断:

编译器不知道我们想要什么类型,可以手动声明类型给点提示:

五.自定义类型

代数数据类型

Algebraic Data Type,是指通过代数运算构造出来的数据结构,其中代数运算有两种:

sum:逻辑或,例如Maybe类型的可能值之间是逻辑或关系

product:逻辑与,例如元组分量之间是逻辑与的关系

例如:

通过逻辑或和逻辑与能造出来任意复杂的数据结构,都可以称为代数数据类型

从地位来看,代数数据类型之于函数式语言,就像代数之于数学,是非常基础的东西。同样,要进行代数运算,先要有数的定义:

map algebraic data type to math

声明

通过关键字来声明自定义类型:

表示类型有2个值构造器(),即类型的值是或者,值构造器本质上是函数:

值构造器的参数(比如的)也被称为项(field),实际上就是参数

既然值构造器是函数,那么模式匹配也可以用于自定义类型:

求面积函数的类型为:

参数类型是,而不是,因为后者只是值构造器,并不是类型

另外,模式匹配都是针对值构造器的,常见的如, , 等都是无参值构造器

递归定义类型

如果一个类型的值构造器的参数(field)是该类型的,那就产生递归定义了

例如List的语法糖:

就是一种递归定义:List是把首项插入到剩余项组成的List左侧

不妨手搓一个:

其中,自定义运算符相当于,都属于值构造器(所以的模式匹配实际上是针对List的值构造器的)。试玩一下:

除了语法上的差异,和List定义()基本一致。再造几个List特色函数:

继续试玩:

派生

只有类(typeclass)的成员才能在GHCi环境直接输出(因为输出前调用),所以,让成为的成员:

通过关键字声明类型派生,让一个类型的值也成为其它类型的成员。试着直接输出值:

干脆把坐标点也抽离出来:

除外,其它几个能够自动添上默认行为的typeclass是。比如派生自后可以通过和来比较值的相等性:

实际上,派生自时自动添的相等性判断就是检查输入参数是否一致:

当然,要求参数也必须是类成员,否则无法自动比较(如果不满足,就会抛个错出来)

和也类似,用来完成字符串与值之间的互相转换:

很有意思,表示成员是可排序的,但默认的排序依据如何确定呢?

首先看类型声明中的次序,或()在一起的,最先出现的值构造器,造出来的值最小,然后再按照类似的规则比较值构造器的参数,所以同样要求参数都得是成员

用来定义枚举类型,即有限集合,要求每个值都有前驱/后继,这样就可以用于Range了,要求值具有上下界,例如:

Record

对于简单的数据类型,比如:

简单的定义就能满足语义需要(我们明确知道二维向量的两个参数是横纵坐标),如果要描述的对象是复杂的东西,比如人有年龄、身高、体重、三围:

这个看着太不直观了,我们添行注释:

想到了什么?这不就是10几个参数的函数嘛!参数巨多还要求顺序,更麻烦的是,这是个数据类型,我们还需要一系列的 :

其它语言里一般怎么处理这种情况?把零散参数组织起来(比如造个对象):

创建一个,语义清楚,并且不用关心参数顺序:

会自动创建一堆,例如:

用起来比单纯的类型定义方便多了

类型参数

类型构造器可以传入参数,返回新的类型。例如:

其中,是类型参数,不是类型,而是类型构造器,具体的才是类型,和都是该类型的值,例如:

这样做能够得到一堆行为相似的类型,从应用场景上来看,带参数的类型相当于泛型,是在具体类型之上的一层抽象,比如经典的:

都支持一些行为(模块定义的各种函数):

与函数并不关心具体类型是什么,算是定义在抽象数据类型上的操作

Maybe与Either

应用场景上,用来表示可能出错的结果,成功就是,失败就是。适用于单一错误原因的场景,比如:

找到了返回类型的下标,找不到就返回,没有第三种结果

单看异常处理的场景,Either更强大一些,一般把失败原因放到,成功结果放到,形式上与非常像,但可以携带任意信息,相比之下,就太含糊了

P.S.JS上下文中,相当于约定成功就返回值,失败返回,只知道失败了,可能不清楚具体原因。相当于约定回调函数的第一个参数携带错误信息,如果不为空就是失败了,具体原因就是该参数的值

类型别名

Type synonyms(类型同义词,即类型别名),之前已经见过了:

通过关键字给类型定义别名,让等价于,从而给类型声明带来语义上的好处,例如:

输入姓名、电话和电话簿,返回电话簿里有没有这条记录。如果不起别名的话,类型声明就只能是这样:

当然,这个场景看起来似乎有些小题大做,为了一个没什么实际作用的东西(类型声明)多做这么多事情。但类型别名的特性是为了提供一种允许类型定义的语义更形象生动的能力,而不是针对具体某个场景,比如:

让类型声明更加易读

替换掉那些重复率高的长名字类型(如)

这种能力能够让类型对事物的描述更加明确

类型别名也可以有参数,比如,自定义的关联列表:

允许任意,保证其通用性,例如:

此时对应的具体类型就是:

类型别名也有类似于柯里化的特性,例如:

如果参数给够就是个具体类型,否则就是带参数的类型

参考资料

Type signature

Pronunciation

单精度与双精度是什么意思,有什么区别?

The algebra (and calculus!) of algebraic data types

Algebraic type sizes and domain modelling

联系ayqy

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180506G18S0R00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券