7.Data Type & Type Parameters
数值类型定义:
1 | data Bool = False | True |
数值构造器(value Constructor):
1 | data Shape = Circle Float Float Float | Rectangle Float Float Float Float |
上面Circle 和 Rectangle 就是俩,他接受若干类型和个数的数值然后返回一个类型对象,他们本质上也是函数
1 | Rectangle :: Float -> Float -> Float -> Float -> Shape |
1 | area :: Shape -> Float |
上面用到了模式匹配(pattern matching)
为了在控制台展示一个类型,要让他得到(deriving)?Show类型
1 | data Shape = Circle Float Float Float | Rectangle Float Float Float Float |
因为数值构造器是函数,所以我们可以做任何高阶函数干的事,比如map, currying.
除了系统自带的类型,我们还可以将自定义类型作为参数类型:
1 | data Point = Point Float Float deriving(Show) |
将数值构造器向外暴露用俩. 通配符,可以把该类型下所有的构造器全部暴露出去
1 | module Shapes ( Point(..) , Shape(..) , area , nudge , baseCircle , baseRect ) where |
你想给构造器中的类型起个有意义的名字,可以用record syntax 语法起别名(实际上也是函数)
1 | data Person = Person { firstName :: String , lastName :: String , age :: Int , height :: Float , phoneNumber :: String , flavor :: String } deriving (Show) |
类型构造器(Type Constructors):
接受类型参数(type parameter)返回一个新类型的函数叫 类型构造器
1 | data Maybe a = Nothing | Just a |
a 是类型参数,它可以是String 也可以是 Int 等。
绝大多数时间,我们不需要指定类型参数,因为haskell自带类型推导。
碰上模棱俩可的情况就要指定一下了,比如:
1 | Just 3 :: Maybe Int |
其实相当于java中的泛型
泛型约束:
1 | (Ord k) => |
k只能是可排序的 实现Ord,书本不建议在类型构造器上加约束,因为对应的所有函数都要加一遍。完全可以在需要的函数上再加约束。
几个常用的deriving
- Show ,就是把数据类型变成字符串
- Read ,就是把字符串读取成数据类型
1 | mysteryDude = "Person { firstName =\"Michael\"" ++ ", lastName =\"Diamond\"" ++ ", age = 43}" |
- Eq , 比较是否相等
- Ord , 排序。先看构造器,再看参数。先声明的构造器更小,相同构造器,比较参数大小。
- Enum , 枚举,对于某个值有前值和后值
1 | ghci> succ Monday |
- Bounded , 有上下限
1 | ghci> minBound :: Day |
类型同义词(Type Synonyms)
就是给一个类型定义一个短名
1 | type PhoneBook = [(String, String)] |
也可以给类型构造器用
1 | type AssocList k v = [(k. v)] |
与data 关键字不同,左边是实际类型(构造器)对象,而前者是统称,非实际对象。
递归构造器
1 | data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord) |
把Con用中缀构造器简化一下(中缀构造器只能以:打头)
1 | data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord) |
然后最头疼的来了,看下数组连接函数混用中缀构造器:
1 | (^++) :: List a -> List a -> List a |
构造器在这种的作用主要是 模式匹配 和 值构造。
类型类/接口(Type Classes)
类型类定义了一些函数的声明(类似接口),这些函数声明构成一定的规则。
所有类型类的实体要实现最少个函数去覆盖类型类中所有函数声明(minimal complete definition )。
1 | class Eq a where |
1 | data TrafficLight = Red | Yellow | Green |
因为类型类里已经定义了不相等的情况,所以实现类里只要定义相等的情况就能完成实现
子类/接口(Subclassing)
其实就是继承了父类的函数声明规则
1 | class (Eq a) => Num a where |
Num必须带有有Eq的规则,也就是类型实体要定义==或/=
泛型类型的类/接口实现
1 | instance (Eq m) => Eq (Maybe m) where |
类型构造器不能直接实现类才怪,如果类型构造器作为类型,那么声明里面每个参数和返回必须是一个确定的(Concrete)类型。带上参数后就行了。
函子(Functor)
开始变魔术了。
1 | class Functor f where |
这里的 f 是一个 类型构造器(type constructor)。
比如 Maybe, 比如 []。
但是对于那种需要多个参数的类型构造器,fmap只对最后一个类型起效,因为函子要的类型构造器只能拿进一个类型,就只能拿构造器的偏函数,如 Map k v的Map k
1 | > fmap (+1) $ Left 1 |
类型的类型(Kind)
1 | ghci> :k Maybe |
* 代表确定的类型
就是说Maybe是拿一个确定类型产生确定类型的函数(构造器)。
所以函子里的 f 的 kind 必须是 * -> *