《Haskell趣学指南》笔记之自定义类型

栏目: 编程语言 · 发布时间: 5年前

内容简介:系列文章值构造器可以直接是一个值,如 True / False 值构造器也可以是一个名字后面加一些类型值构造器本质上是一个返回某数据类型值的函数,所以 Circle 和 Rectangle 不是类型,是函数:

系列文章

自定义数据类型

一、用 data 关键字

data 类型名 = 值构造器 | 值构造器
data Bool = False | True
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
复制代码

值构造器可以直接是一个值,如 True / False 值构造器也可以是一个名字后面加一些类型

值构造器本质上是一个返回某数据类型值的函数,所以 Circle 和 Rectangle 不是类型,是函数:

ghci> :t Circle 
Circle :: Float -> Float -> Float -> Shape 
ghci> :t Rectangle 
Rectangle :: Float -> Float -> Float -> Float -> Shape
复制代码

然后就可以使用这个类型了

area :: Shape -> Float 
-- 注意下面的模式匹配
area (Circle _ _ r) = pi * r ^ 2 
area (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
-- 注意下面的 Circle 和 Reactangle 的位置
ghci> area $ Circle 10 20 10 
314.15927 
ghci> area $ Rectangle 0 0 100 100 
10000. 0
复制代码

但是现在如果你在 ghci 里输入 Circle 1 1 5 会报错,因为 Shape 不是 Show 类型类的实例,不能被 show 函数调用。 解决办法是在 data Shape 那句话的后面加一句 deriving (Show)

data Shape = Circle Float Float Float | Rectangle Float Float Float Float  
    deriving (Show)
复制代码

改进

Circle 函数接受三个 Float 参数,这三个参数前面两个是圆心的坐标,最后一个是半径。

我们用 Point 类型来优化 Shape,使得它更已读:

data Point = Point Float Float deriving (Show) 
-- 注意左边的 Point 是类型名,右边的 Point 是值构造器名(类似与构造函数么?)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

area :: Shape -> Float 
area (Circle _ r) = pi * r ^ 2 
-- 注意下面的模式匹配
area (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)

ghci> area (Rectangle (Point 0 0) (Point 100 100)) 
10000. 0 
ghci> area (Circle (Point 0 0) 24) 
1809. 5574
复制代码

导出

module Shapes ( 
    Point(..) , -- 看这里
    Shape(..) , -- 看这里
    area , 
) where
复制代码

其中 Shape(..) 的意思是导出 Shape 以及 Shape 所有的值构造器,也可以写成 Shape(Circle, Rectangle) 。 当然也可以不写括号这一部分,这样别人就不能使用 Circle 和 Rectangle 函数了。

二、用 data + 记录语法 record syntax

data Person = Person {
    firstName::String,
    age::Int,
    height::Float,
    phoneNumber::String,
    flavor::String
} deriving (Show)
复制代码

这种语法会自动创建 firstName 等函数、允许按字段取值。

ghci> :t firstName 
firstName :: Person -> String
复制代码

类型参数(很像泛型)

data Maybe a = Nothing | Just a
复制代码

Maybe 是一个类型构造器(不是类型),a 是类型参数,a 可以是 Int / Char / ...,而 Just 是个函数。

由于 Haskell 支持类型推导,所以我们只用写 Just 'a',Haskell 就知道这是一个 Maybe Char 类型。

其实列表 [] 就是一个类型构造器,[Int] 存在,但是不存在类型 []。

Maybe 类型的使用示例:

ghci> Just "Haha"
Just "Haha" 
ghci> :t Just "Haha" 
Just "Haha" :: Maybe [Char] 
ghci> :t Just 84 
Just 84 :: (Num t) => Maybe t 
ghci> :t Nothing 
Nothing :: Maybe a 
ghci> Just 10 :: Maybe Double 
Just 10. 0 
复制代码

data 支持类约束,但是永远不要用

data (Ord k) => Map k v = ...
复制代码

书上说这只会徒增无谓的代码。

如何让一个 type 成为类型类的实例

只需要在 data 语句后面加上 deriving (Eq) 即可。

在一个类型派生为Eq的实例后,就可以直接使用==或/=来判断它们的值的相等性了。 Haskell会先检查两个值的值构造器是否一致(这里只有单值构造器),再用==来检查其中的每一对字段的数据是否相等。 唯一的要求是:其中所有字段的类型都必须属于Eq类型类。

加上 deriving (Eq, Show, Read) 就可以成为三者的实例。

Enum 类型类

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
-- 或者加上 typeclass
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday           
    deriving (Eq, Ord, Show, Read, Bounded, Enum) -- 综合目前所学
    
ghci> Wednesday
Wednesday 
ghci> show Wednesday
"Wednesday" 
ghci> read "Saturday" :: Day 
Saturday
ghci> Saturday == Sunday 
False 
ghci> Saturday == Saturday 
True 
ghci> Saturday > Friday 
True 
ghci> Monday ` compare` Wednesday 
LT
ghci> minBound :: Day 
Monday 
ghci> maxBound :: Day 
Sunday
复制代码

类型别名

type String = [Char] -- 注意不是 data 是 type
-- 支持参数
type IntMap v = Map Int v
-- 等价于 point-free 风格的下面代码
type IntMap = Map Int
复制代码

Either a b 类型

data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
复制代码

书上例子挺好懂,大概意思是错了就返回 Left "error message",对了就返回 Right "data message"。 不过我还不明白我怎么知道 Right "data message" 是 Right 构造出来的呢?

递归数据结构

data List a = Empty | Cons a (List a) 
    deriving (Show, Read, Eq, Ord)
    
ghci> Empty 
Empty 
ghci> 5 ` Cons` Empty 
Cons 5 Empty
ghci> 4 ` Cons` (5 ` Cons` Empty) 
Cons 4 (Cons 5 Empty) 
ghci> 3 ` Cons` (4 ` Cons` (5 ` Cons` Empty)) 
Cons 3 (Cons 4 (Cons 5 Empty))
复制代码

自制一个列表

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

ghci> 3 :-: 4 :-: 5 :-: Empty 
3 :-: (4 :-: (5 :-: Empty)) 
ghci> let a = 3 :-: 4 :-: 5 :-: Empty 
ghci> 100 :-: a 
100 :-: (3 :-: (4 :-: (5 :-: Empty)))

infixr 5  ^++ 
(^++) :: List a -> List a -> List a 
    Empty ^++ ys = ys 
    (x :-: xs) ^++ ys = x :-: (xs ^++ ys)

ghci> let a = 3 :-: 4 :-: 5 :-: Empty 
ghci> let b = 6 :-: 7 :-: Empty 
ghci> a ^++ b 
3 :-: (4 :-: (5 :-: (6 :-: (7 :-: Empty))))
复制代码

从这个例子我大概理解黄玄说的『函数式就是 symbolism』

这一年里一直在不断刷新自己对「FP 是什么」这个问题的回答… 之前觉得说「靠近/源自数学或者逻辑」吧,难道命令式/OO 的语言就不是描述数学和逻辑? 这种解释本身不明白这个差别的人大概听了也还是不会明白…… 今天突然觉得「(尽可能的)symbolism(符号主义)」也是个不错的描述,从 FP 语言的历史来看,主要的两个祖宗 Lisp 和 ML(LCF)都起家于符号主义 AI。 即使编程语言都是符号化的,但相比于寄托于各类外置的作用,越是「FP」越是 尽可能得希望程序的行为是可以从符号中详尽的,这因此带来了大家说的「声明式」、「可预测性」和「确定性」。

自制一棵二叉搜索树

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show)
-- 下面这个函数用来创建节点
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)
-- 下面这个函数用来判断元素是否在树中  
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
    
-- 使用

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)
    )
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一路编程

一路编程

史蒂夫·富特 (Steven Foote) / 佟达 / 电子工业出版社 / 2017-1-1 / CNY 65.00

《一路编程》是一本编程入门书籍,然而,如果以书中所讲内容作为入门标准,估计十有八九的在职程序员都不能算已入门。现代软件开发,已经不仅仅是写出正确的代码这么简单,环境、依赖、构建、版本、测试及文档,每一项都对软件是否成功交付起到至关重要的作用,这些都是每一个程序员在开发软件过程中必备的技能。《一路编程》对于上述的每一种技能都做了简洁而精练的介绍,以满足最基本的日常软件开发。换句话说,《一路编程》实际......一起来看看 《一路编程》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具