Haskell学习笔记: type and typeclasses

Type

常见类型及注意事项

  • type首字母大写
  • Int 表示整数,Int 是有界的,上限一般是 2147483647,下限是 -2147483648。
  • Integer也表示整数,但是无界。效率不如Int高。
  • Float 表示单精度的浮点数。(存储包括小数点的九位)
  • Double 表示双精度的浮点数。(存储包括小数点的十八位)
  • Bool 表示布尔值,它只有两种值:True 和 False。
  • Char 表示一个字符。一个字符由单引号括起,一组字符的 List 即为字符串。
  • Tuple 的类型取决于它的长度及其中项的类型。注意,空 Tuple 同样也是个类型,它只有一种值:()。
  • 凡是类型首字母大写。
  • 使用 :t 命令后跟任何可用的表达式,即可得到该表达式的类型
  • Haskell 支持类型推导。

Type variables

head 函数的类型的类型如下:

ghci> :t head  
head :: [a] -> a
  • 其中a为类型变量,可以使任意的类型。
  • 使用到类型变量的函数被称作"多态函数 "(polymorphic functions)。

Typeclass

让我们来研究下 == 函数的类型声明:

ghci> :t (==)  
(==) :: (Eq a) => a -> a -> Bool

Note: 判断相等的==运算符是函数,+-*/之类的运算符也是同样。在缺省条件下,它们多为中缀函数。若要检查它的类型,就必须得用括号括起使之作为另一个函数,或者说以首码函数的形式调用它。

  • 怎么理解上面的类型声明?相等函数取两个相同类型的值作为参数并回传一个布尔值,而这两个参数的类型同在 Eq 类之中(即类型约束)。
  • 其中 => 符号。它左边的部分叫做类型约束(class constraint)。
  • Eq 这一 Typeclass 提供了判断相等性的接口,凡是可比较相等性的类型必属于 Eq class。

基本的typeclass

  1. Eq
  • 包含可判断相等性的类型。除IO类型和函数以外的所有Haskell标准类型都属于 Eq,所以它们都可以判断相等性。
  1. Ord
    包含可比较大小的类型。除了函数以外,我们目前所谈到的所有类型都属于 Ord 类。Ord 包中包含了<, >, <=, >= 之类用于比较大小的函数。类型若要成为Ord的成员,必先加入Eq家族。
  2. Ordering
    GT, LT or EQ, meaning greater than, lesser than and equal, respectively.
    compare函数取两个同类型属于Ord类的数进行比较,返回一个Ordering。
  3. Show
    Show的成员为可用字符串表示的类型,除了函数以外其它type都为其成员,最常用的函数是show。它可以取任一Show的成员类型并将其转为字符串。
  4. Read
    是与 Show 相反的 Typeclass。read 函数可以将一个字符串转为 Read 的某成员类型。需要在一个表达式后跟:: 的类型注释,以明确其类型。
ghci> read "5" :: Int  
5  
ghci> read "(3, 'a')" :: (Int, Char)  
(3, 'a')

或者根据read 后跟的那部分,ghci自动辨认类型。

ghci> read "True" || False  
True  
ghci> read "8.2" + 3.8  
12.0  
  1. Enum
    Enum 的成员都是连续的类型 – 也就是可枚举。在 Range 中用到它的成员类型,分别可以通过 succ 函数和 pred 函数得到。该 Typeclass 包含的类型有:(), Bool, Char, Ordering, Int, Integer, Float 和 Double。如:
ghci> succ 'B'  
'C'
ghci> [LT .. GT]  
[LT,EQ,GT] 
  1. Bounded
    Bounded 的成员都有一个上限和下限。
ghci> minBound :: Int  
-2147483648  
ghci> maxBound :: Char  
'\1114111'  
ghci> maxBound :: Bool  
True  
ghci> minBound :: Bool  
False

另外,如果Tuple中所有项都属于Bounded的Typeclass,那么它也属于Bounded。

  1. Num
    表示数字的 Typeclass,它的成员类型都具有数字的特征。类型只有亲近 Show 和 Eq,才可以加入 Num。
  2. Integral
    同样是表示数字的 Typeclass。Num 包含所有的数字:实数和整数。而 Integral 仅包含整数,其中的成员类型有 Int 和 Integer。
  3. Floating
    仅包含浮点类型:Float 和 Double。

Note:fromIntegral函数用于处理数字,其类型声明为: fromIntegral :: (Num b, Integral a) => a -> b。将一个整数变为更广泛的类型num。

构造自己的type

  • 关键字:data
data Bool = False | True

data = 的左端标明类型的名称即 Bool,= 的右端就是值构造子 (Value Constructor),它们明确了该类型可能的值。| 读作"或"
以上可以理解为:Bool 类型的值可以是 True 或 False。

  • 类型名和值构造子的首字母必大写。
  • 项(field):可以在值构造子后面选择性的加入一些type(s),如下例中Circle 的值构造子有三个项,也可以理解成有三个参数,皆为浮点数。
data Shape = Circle Float Float Float

值构造子本质函数,可以返回一个类型的值,如下类型声明。

ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape

由于值构造子是个函数,因此我们可以拿它交给 map,拿它不全调用,以及普通函数能做的一切。

ghci> map (Circle 10 20) [4,5,6,6]
[Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]
  • 使用的模式匹配针对的都是值构造子。
  • 让我们的 Shape 类型成为 Show 类型类的成员。可以这样修改:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)

之后Haskell 就会自动将该类型至于 Show 类型类之中。

  • 你可以把你的数据类型导出到模块中。只要把你的类型与要导出的函数写到一起就是了。再在后面跟个括号,列出要导出的值构造子,用逗号隔开。如要导出所有的值构造子,那就写个…
module Shapes
(  Shape(..),
···
)where
  • 我们可以选择不导出任何 Shape 的值构造子。注意,值构造子只是函数而已,如果不导出它们,就拒绝了使用我们模块的人调用它们。但可以使用其他返回该类型的函数,来取得这一类型的值。

Record Syntax

data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     , height :: Float
                     , phoneNumber :: String
                     , flavor :: String
                     } deriving (Show)
  • 注意:格式花括号括起, firstName,lastName等都是函数,= 左边的Person是类型名,右边的Person是值构造子。
  • 用record syntax 还有一个好处是某类型赋值时,我们不需要按照值构造子后项的顺序一模一样的给出值,完整的列出来就行。
data Car = Car String String Int deriving (Show)
ghci> Car "Ford" "Mustang" 1967  
Car "Ford" "Mustang" 1967  

data Car = Car {company :: String, model :: String, year :: Int} deriving (Show)  

Type parameters

类型构造子可以取类型作参数,产生新的类型。

data Maybe a = Nothing | Just a

这里的a就是个类型参数。也正因为有了它,Maybe 就成为了一个类型构造子。在它的值不是 Nothing 时,它的类型构造子可以搞出 Maybe Int,Maybe String 等等诸多态别。

  • 注意:在data声明中最好不要加入类约束,因为一旦加入,之后任何函数含有此data类型函数类型声明都同样需要加入此类约束。

Derived instances

  • 如何自动生成几个类型类的 instance?(Eq, Ord, Enum, Bounded, Show, Read)。只要我们在构造类型时在后面加个 deriving(派生)关键字,Haskell 就可以自动地给我们的类型加上这些行为。
data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq, Show)
                     
let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> mikeD
Person {firstName = "Michael", lastName = "Diamond", age = 43}

ghci> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}
ghci> let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}
ghci> mca == adRock
False
ghci> mikeD == adRock
False
ghci> mikeD == mikeD
True

read "Person {firstName =\"Michael\", lastName =\"Diamond\", age = 43}" :: Person
Person {firstName = "Michael", lastName = "Diamond", age = 43}

如果上文没加deriving (Eq, Show),Haskel就报错,说不知道怎么办,没有?。

  • Ord 类型类 。
    首先,判断两个值构造子是否一致,如果是,再判断它们的参数,前提是它们的参数都得是 Ord 的 instance。
    其次,若两值构造子 不一致,排在前面的小。
data Bool = False | True deriving (Ord)
ghci> True `compare` False
GT
ghci> True > False
True
  • Maybe a 数据类型中
    值构造子 Nothing 在 Just 值构造子前面,所以一个 Nothing 总要比 Just something 的值小。
ghci> Nothing < Just 100
True
ghci> Just 3 `compare` Just 2
GT
ghci> Just 100 > Just 50
True

Type synonyms

  • type 关键字,给既有类型取一个别名。
type String = [Char]
  • 类型别名也是可以有参数的,如果你想搞个类型来表示关联 List,但依然要它保持通用,好让它可以使用任意类型作 key 和 value,我们可以这样:
type AssocList k v = [(k,v)]
  • 我们可以用不全调用来得到新的函数,同样也可以使用不全调用得到新的类型构造子。如果我们要一个表示从整数到某东西间映射关系的类型,我们可以这样:
type IntMap v = Map Int v
type IntMap = Map Int

二参数据类型

  • Either a b
data Either a b = Left a | Right b

多出来的一个参数可以用来报错,?

Recursive data structures

如我们先前看到的,一个 algebraic data type 的构造子可以有好几个 field,其中每个 field 都必须有具体的型态。因此,我们能定义一个型态,其中他的构造子的 field 的型态是他自己。这样我们可以递归地定义下去,某个型态的值便可能包含同样型态的值,进一步下去他还可以再包含同样型态的值。

infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)

ghci> 3 :-: 4 :-: 5 :-: Empty
(:-:) 3 ((:-:) 4 ((:-:) 5 Empty))
  • infixr 指定了他应该是 left-associative 或是 right-associative,还有他的优先级。例如说,* 的 fixity 是 infixl 7 *,而 + 的 fixity 是 infixl 6。代表他们都是 left-associative。
  • 用特殊字符 :-:来定义函数,这样他们就会自动具有中缀的性质。
  • 5 :-: Empty = ( :-: ) 5 Empty
    就像5 : Empty = ( : )5 Empty
    因为现在 :-: 是我们类型中List中的一个constructor,而haskell 做类型匹配的时候实际上是匹配constructor。
    (其实还是没绕清楚最后一句什么意思,sad)

二元搜索树 (binary search tree)

  • 定义一棵树
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
  • 检查某个元素x是否已经在这棵树中
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
   | x == a = True
   | x < a  = treeElem x left
   | x > a  = treeElem x right
  • 修改(插入)树
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
     | x == a = Node x left right
     | x < a  = Node a (treeInsert x left) right
     | x > a  = Node a left (treeInsert x right)

singleton函数:一做一个含有两棵空子树的节点的函数4

  • 创造一棵树
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))

在 foldr 中,treeInsert 是做 folding 操作的函数,而 EmptyTree 是起始的 accumulator,nums 则是要被走遍的 List。

创造自己的typeclass

  • 手工打造自己的type属于show的typeclass:
data TrafficLight = Red | Yellow | Green

instance Eq TrafficLight where
   Red == Red = True
   Green == Green = True
   Yellow == Yellow = True
   _ == _ = False
  • 对于有参数的类型,一个错误的自定义类型类如下:
instance Eq (Maybe m) where
   Just x == Just y = x == y
   Nothing == Nothing = True
   _ == _ = False

注意到,我们虽然注意到了明确类型Maybe为一个确实值Maybe m,却没有保证这个确定值m属于Eq。为此,修改以下为正确定义:

instance (Eq m) => Eq (Maybe m) where
   Just x == Just y = x == y
   Nothing == Nothing = True
   _ == _ = False
  • 创造一棵树
ghci> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
Node 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))

在 foldr 中,treeInsert 是做 folding 操作的函数,而 EmptyTree 是起始的 accumulator,nums 则是要被走遍的 List。

:info

在 ghci 中输入 :info Num 会告诉你这个 typeclass 定义了哪些函数,还有哪些类型属于这个 typeclass。
:info 也可以查找类型跟类型构造子的信息。如果你输入 :info Maybe。他会显示 Maybe 所属的所有 typeclass。:info 也能告诉你函数的类型声明。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值