1 / 41

定义新类型

定义新类型. 本章介绍如何使用代数类型描述数据。 Slides borrowed from J. Hughes. 为什么定义新类型. 前面介绍了使用 Haskell 值表示数据的例子,如使用 [(Borrower, Book)] 表示借阅卡片库,其中 type Borrower = String type Book = String 但是,有些数据难以用我们已学的类型表示。. 例 : 使用什么类型表示一个星期 : 星期一, ..., 星期日 (Monday, Tuesday, Wednesday etc.). 使用串?

terrel
Download Presentation

定义新类型

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 定义新类型 本章介绍如何使用代数类型描述数据。 Slides borrowed from J. Hughes

  2. 为什么定义新类型 前面介绍了使用Haskell 值表示数据的例子,如使用[(Borrower, Book)]表示借阅卡片库,其中 type Borrower = String type Book = String 但是,有些数据难以用我们已学的类型表示。 例: 使用什么类型表示一个星期: 星期一,..., 星期日(Monday, Tuesday, Wednesday etc.). 使用串? Monday++Tuesday? day = “ahha”? 使用数字? Monday + Tuesday?

  3. 定义新类型 我们可以定义只包含一个星期七天的类型: data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Show) 新类型的值 (大写字母开始) 新类型名 定义 == 和 show 新类型的值各不相同,它们不能相加,不能连接。这种类型称为代数数据类型。

  4. 新定义类型上的函数 • 新类型上的函数可以通过模式匹配定义: • weekDay :: Day -> Bool • weekDay Monday = True • weekDay Tuesday = True • weekDay Wednesday = True • weekDay Thursday= True • weekDay Friday = True • weekDay Saturday = False • weekDay Sunday = False

  5. 类型 Bool的定义 布尔类型的定义 data Bool = True | False deriving (Eq, Show) True && p = p False && p = False

  6. 类型 List的定义 data List a = Nil | a : List a deriving (Eq, Show) 考虑整数列表集合[Int]的定义 (1)空列表Nils属于 [Int]; (2)如果 x::Int, xs::[Int], 那么x:xs是[Int]的元素; 二叉树定义 data Tree a = Nil | Node a (Tree a) (Tree a) deriving (Eq, Show) depth :: Tree a -> Int depth Nil = 0 depth (Node n t1 t2) = 1+ max (depth t1)(depth t2)

  7. 乘积类型 多元组类型可以用具有多个分量的代数类型代替,又称为乘积类型。例如, data People = Person Name Age 其中Name 和Age 分别是String 和Int 的别名: type Name = String type Age = Int 类型People 的定义可读作: 构造People一个元素需要两个值:一个是类型是Name的值st,另一个是类型为Age的值n,由此形成People的值Person st n.

  8. 代数数据类型的一般形式 代数类型定义的一般形式: data Typename = Con1 t11 … t1k1 | Con2 t12 … t2k2 … | Conm tm1 … tmkm 类型 构造符

  9. 表示多种选择 代数类型便于表示多种选择: 例:电话记录的表示 type History = [(Event, Time)] type Time = Int data Event = Call String | Hangup 拨叫号码 E.g. Call ”031-7721001”, Hangup, etc.

  10. 抽取拨叫号码明细表 例:抽取拨叫号码明细表。仍然使用模式匹配: calls :: History -> [(String, Time, Time)] calls ((Call number, start) : (Hangup, end) : history) = (number, start, end) : calls history calls [(Call number, start)] = [] -- 正在进行的通话 calls [] = []

  11. 算术表达式 设想有一个学算术程序:程序显示一个算术式,并检查结果是否正确。 What is (1+2)*3? 8 Sorry, wrong answer! 表达式(1+2)*3 是数据,且不同于9。 如何表示这样的数据呢? 使用串?? ”1+2”++”3”表示什么? ”1+hello world**”表示什么?

  12. 表达式建模 我们可以使用代数类型为算术表达式(结构)建模 在每个构造符中 应该提供什么? • 一个表达式可以是: • 一个数 n • 一个变量 x • 表达式相加 a+b • 表达式相乘 a*b data Expr = Num | Var | Add | Mul

  13. 表达式建模 我们可以使用代数类型为算术表达式(结构)建模 • 一个表达式可以是: • 一个数 n • 一个变量 x • 表达式相加 a+b • 表达式相乘 a*b data Expr = Num Int | Var String | Add Expr Expr | Mul Expr Expr 递归代数类型

  14. 例子 data Expr = Num Int | Var String | Add Expr Expr | Mul Expr Expr 表达式: 在代数类型中的表示: 2 Num 2 2+2 Add (Num 2) (Num 2) (1+2)*3 Mul (Add (Num 1) (Num 2)) (Num 3) 1+2*3 Add (Num 1) (Mul (Num 2) (Num 3))

  15. 问题 试定义一个计算表达式的值的函数: eval :: Expr -> Int 例: eval (Add (Num 1) (Mul (Num 2) (Num 3))) 7 不考虑带有变量的表达式。

  16. 计算表达式 eval :: Expr -> Int 使用模式匹配: eval (Num n) = eval (Add a b) = eval (Mul a b) = 如何定义等式的右边? a 和 b 的类型是 Expr.

  17. 计算表达式 eval :: Expr -> Int eval (Num n) = n eval (Add a b) = eval a + eval b eval (Mul a b) = eval a * eval b 递归定义的类型上的函数 往往是递归的。

  18. 计算带变量的表达式值 eval :: Expr -> [(String, Int)] -> Int eval (Num n) subst = Eval (Var x) subst = eval (Add a b) subst = eval (Mul a b) subst =

  19. 符号微分 一个表达式的微分是一个表达式: differentiate :: Expr -> String -> Expr differentiate (Num n) x = Num 0 differentiate (Var y) x | x==y = Num 1 | x/=y = Num 0 differentiate (Add a b) x = Add (differentiate a x) (differentiate b x) differentiate (Mul a b) x = Add (Mul a (differentiate b x)) (Mul b (differentiate a x)) 微分变量

  20. d (2*x) = 2 dx differentiate (Mul (Num 2) (Var ”x”)) ”x” Add (Mul (Num 2) (differentiate (Var ”x”) ”x”)) (Mul (Var ”x”) (differentiate (Num 2) ”x”)) Add (Mul (Num 2) (Num 1)) (Mul (Var ”x”) (Num 0)) 2*1 + x*0

  21. 格式化表达式 将表达式格式化为更易读的字符串: instance Show Expr where show = formatExpr formatExpr (Num n)= show n formatExpr (Var x)= x formatExpr (Add a b) = ”(”++formatExpr a ++ ”+” ++ formatExpr b++”)” formatExpr (Mul a b)= ”(”++formatExpr a ++ ”*” ++ formatExpr b++”)” show (Mul (Num 1) (Add (Num 2) (Num 3))) ”((1*2)+3)”

  22. 括号问题 那些括号是必需的? 1+(2+3) 1+(2*3) 1*(2+3)

  23. 括号问题 NO! 那些括号是必需的? 1+(2+3) 1+(2*3) 1*(2+3) 那些表达式可能需要括起来? 什么时候需要括起来? NO! YES! 加法 加法表达式在乘法内部

  24. 解决括号问题 给 formatExpr 一个额外参数用以说明其原输入所处的上下文。 data Context = Multiply | AnyOther formatExpr :: Expr -> Context -> String formatExpr (Add a b) Multiply = ”(” ++ formatExpr (Add a b) AnyOther ++ ”)” formatExpr (Mul a b) _ = formatExpr a Multiply ++ ”*” ++ formatExpr b Multiply

  25. 表示失败 许多函数对于某些输入没有结果。 例: 表上的查找. type Table = [(String, Int)] lookup ”y” [(”x”,1), (”y”,2)] Found 2 lookup ”z” [(”x”,1), (”y”,2)] Notfound data MaybeInt = Found Int | Notfound

  26. 失败的通用类型 我们无需对每种情况定义一个包含失败的类型,可以定义一个通用的类型Maybe: data Maybe a = Just a | Nothing deriving (Eq, Show) 例: Just 2, Just 3, Nothing :: Maybe Int Just ”hello”, Nothing :: Maybe String 类型参数. 表示有结果 表示没有结果

  27. 用代数类型实现查找 代数类型不仅可以表示多种选择,还可以改进程序的效率。下面以查找为例说明。

  28. John Hughes 1001 Mary Sheeran 1013 Rogardt Heldal 1057 Lars Pareto 1158 查找问题 一个查找表示关键字及其相关信息的集合。 例如,电话号码本是姓名(关键字)和电话的集合。 问题:给定一个查找表和一个关键字,找出和关键字相关的信息。

  29. 使用列表的查找表 关键字和相关信息都可以具有任意的类型,故这两个类型作为参数: type Table a b = [(a, b)] lookup :: Ord a => a -> Table a b -> Maybe b lookup key [] = Nothing lookup key ((k,v):t) | key==k = Just v | otherwise = lookup key t [(”x”,1), (”y”,2)] :: Table String Int lookup ”y” … Just 2

  30. 快速查找 顺序查找需要检查列表的每个元素,效率很低。 假定查找表按照关键字有序,则可以先检查表中间的记录,如果它不是查找的纪录,则下一步或者到前半部分查找或者到后半部分查找。 Aaboen A Hughes? Nilsson Hans Östvall Eva

  31. 查找表的表示 • 需要快速将表分解: • 处于中间记录前的一个查找子表; • 中间纪录; • 后半部分查找表。 Aaboen A Nilsson Hans data Table a b = Join (Table a b) a b (Table a b) Östvall Eva

  32. 问题 上述代数类型定义合理吗? data Table a b = Join (Table a b) a b (Table a b)

  33. 问题 递归代数类型定义没有“基”。 data Table a b = Join (Table a b) a b (Table a b) | Empty 加上基:空表

  34. 有序表的查找 • 查找方法: • 如果表空,则查找失败; • 比较给定关键字与中间记录关键字; • 如果相等,则查找成功,返回相应信息; • 如果给定关键字小于中间关键字,则继续到前半部分查找表查找; • 如果给定关键字大于中间关键字,则继续到后半部分查找表查找。

  35. 查找函数 定义查找函数 lookup :: Ord a => a -> Table a b -> Maybe b data Table a b = Join (Table a b) a b (Table a b) | Empty

  36. 查找函数 lookup :: Ord a => a -> Table a b -> Maybe b lookup key Empty = Nothing lookup key (Join left k v right) | key == k = Just v | key < k = lookup key left | key > k = lookup key right 递归函数!

  37. 构造查找表 查找表可以通过不断插入记录完成: insert :: Ord a => a -> b -> Table a b -> Table a b 插入必须保持查找表的有序性。 方法:比较待插入关键字与中间关键字,如果相等,则替换相应信息,否则根据比较结果插入前半部分或者后半部分。

  38. 定义插入 insert key val Empty = Join Empty key val Empty insert key val (Join left k v right) = | key == k = Join left k val right | key < k = Join (insert key val left) k v right | key > k = Join left k v (insert key val right)

  39. 查找效率 平均比较次数:在1000个记录的查找表上 使用列表: 平均500 (=1000/2)次比较 使用代数类型: 平均10 (=lg 1000)次比较

  40. 小结 • Haskell提供定义新的代数类型的方法,便于表示多种选择的数据。代数类型上的函数可以使用模式匹配定义。 • 代数类型可以包含多个组成部分,可以使递归的,可以带有参数。 • 选择合适的数据类型可以改变程序的运行效率。

  41. 习题 • 证明列表概括 [f x | x <- xs, px] 可以用高阶函数map and filter表示. • 定义下列高阶函数all, any, takeWhile 和dropWhile. • 利用foldr定义 map f和 filter p. • 定义带有下列比较函数参数的归并排序函数: • ordering :: a -> a -> Ordering • 5.定义一个以结点信息类型为参数的二叉树类型 • a) 将二叉树定义成类 Show 和 Eq的特例 (不使用deriving)。 • b) 定义中序遍历函数:先遍历左子树,访问根结点,再遍历右子树。 • c) 定义一个判定二叉树是否完全二叉树的函数.

More Related