- 💬 写在前面:本章我们将继续介绍 OCaml 的基本特性,自定义类型、异常处理和模块。掌握了这些内容后,编写基本程序应该不会有太大困难。接下来的两节将学习函数式编程中常用的两种编程风格 —— 递归函数和高阶函数。
目录
0x00 自定义类型
0x01 异常处理(exception)
0x02 模块(module)
0x00 自定义类型
可以使用关键字 type 来定义新类型。
首先,可以利用 type 为已有类型赋予新的名称:
type var = string
type vector = float list
type matrix = float list listtype var = string
type vector = float list
type matrix = float list list
或者可以创建一个新的值集合并将其定义为类型。
例如,可以如下定义表示 "星期几" 集合的类型 days。
# type days = Mon | Tue | Wed | Thu | Fri | Sat | Sun;;
# Mon;;
- : days = Mon
# Tue;;
- : days = Tue
在关键字 type 后面写上要定义的类型名称 (days),在等号后面列出属于该类型的值,用 | 分隔。
Mon, . . . , Sun 被称为 构造函数 (constructor),分别表示 days 类型的不同值。
在 OCaml 中,类型名称以小写字母开头,而构造函数以大写字母开头。
定义了新类型之后,可以定义对该类型的值进行操作的函数。
例如,可以定义一个函数 nextday,该函数接收一个星期几作为输入并返回下一个星期几。
如下所示:
# let nextday d =
match d with
| Mon -> Tue | Tue -> Wed | Wed -> Thu | Thu -> Fri
| Fri -> Sat | Sat -> Sun | Sun -> Mon ;;
val nextday : days -> days = <fun>
# nextday Mon;;
- : days = Tue
也可以定义带有其他值作为参数的构造函数。
例如,可以定义一个类型 shape,其值可以是矩形或圆形:
# type shape = Rect of int * int | Circle of int;;
type shape = Rect of int * int | Circle of int
表示矩形的构造函数 Rect 被定义为具有宽度和高度的参数,
而表示圆形的构造函数 Circle 被定义为具有半径的参数。
属于这个定义类型的值的例子如下:
# Rect (2,3);;
- : shape = Rect (2, 3)
# Circle 5;;
- : shape = Circle 5
可以这样编写一个函数来计算上述定义的图形的面积:
# let area s =
match s with
Rect (w,h) -> w * h
| Circle r -> r * r * 3;;
val area : shape -> int = <fun>
# area (Rect (2,3));;
- : int = 6
# area (Circle 5);;
- : int = 75
在上面的例子中,为了方便起见,将圆周率的值近似为3。
也可以通过归纳法来定义类型。例如,之前我们讲过的整数列表集合:
在 OCaml 中,可以如下定义上述整数列表集合:
# type intlist = Nil | Cons of int * intlist;;
type intlist = Nil | Cons of int * intlist
集合的名称 (类型) 称为 intlist,并定义了创建集合元素的两种方法。
首先是 ,它是表示空列表的构造子。
Cons 是一个构造子,它在给定列表的开头添加一个元素以创建新列表。
例如,可以创建如下列表:
# Nil;;
- : intlist = Nil
# Cons (1, Nil);;
- : intlist = Cons (1, Nil)
# Cons (1, Cons (2, Nil));;
- : intlist = Cons (1, Cons (2, Nil))
例如,Cons (1, Cons (2, Nil)) 表示列表 。
现在我们可以编写处理列表的函数了,计算列表长度的函数如下所示:
# let rec length l =
match l with
| Nil -> 0
| Cons (_, l’) -> 1 + length l’;;
val length : intlist -> int = <fun>
# length (Cons (1, Cons (2, Nil)));;
- : int = 2
让我们尝试用 OCaml 数据类型来定义之前定义的整数表达式:
可以将上述语法结构定义如下:
type exp =
Int of int
| Minus of exp * exp
| Plus of exp * exp
| Mult of exp * exp
| Div of exp * exp
例如, 被表达为以下形式::
# Mult(Plus(Int 1, Int 2), Div(Int 3, Int 3));;
- : exp = Mult (Plus (Int 1, Int 2), Div (Int 3, Int 3))
表示整数表达式含义的归纳规则可以通过递归函数来实现:
# let rec eval exp =
match exp with
| Int n -> n
| Plus (e1, e2) -> (eval e1) + (eval e2)
| Mult (e1, e2) -> (eval e1) * (eval e2)
| Minus (e1, e2) -> (eval e1) - (eval e2)
| Div (e1, e2) ->
let n1 = eval e1 in
let n2 = eval e2 in
if n2 <> 0 then n1 / n2
else raise (Failure "division by 0");;
val eval : exp -> int = <fun>
# eval (Mult (Plus (Int 1, Int 2),
Div (Int 3, Int 3)));;
- : int = 3
这是直接将语义结构的定义转换为递归函数。
当除以 0 时,由于其含义未定义,因此引发了异常。
0x01 异常处理(exception)
在 OCaml 中,如果计算表达式导致 运行时错误 (runtime error) ,
例如尝试将某个数除以 0,会引发 Division_by_zero 异常:
# let div a b = a / b;;
val div : int -> int -> int = <fun>
# div 10 5;;
- : int = 2
# div 10 0;;
Exception: Division_by_zero.
要处理运行时发生的异常,可以使用 try ... with 结构:
# let div a b =
try
a / b
with Division_by_zero -> 0;;
val div : int -> int -> int = <fun>
# div 10 5;;
- : int = 2
# div 10 0;;
- : int = 0
可以使用关键字 exception 来定义并使用新的异常,如下所示:
# exception Fail;;
exception Fail
# let div a b =
if b = 0 then raise Fail
else a / b;;
val div : int -> int -> int = <fun>
# div 10 5;;
- : int = 2
# div 10 0;;
Exception: Fail.
# try
div 10 0
with Fail -> 0;;
- : int = 0
0x02 模块(module)
模块 (module) 是类型和值的集合。它们将相关功能组合在一起并隐藏其内部实现。
例如,我们可以像下面这样通过模块定义 队列 (queue) 数据结构。
module IntQueue = struct
type t = int list
exception E
let empty = []
let enq q x = q @ [x]
let is_empty q = q = []
let deq q =
match q with
| [] -> raise E
| h::t -> (h, t)
let rec print q =
match q with
| [] -> print_string "\n"
| h::t -> print_int h; print_string " "; print t
end
这里实现了一个基于列表的队列,模块的用户可以不了解具体实现细节,
仍然可以像下面这样使用它:
let q0 = IntQueue.empty
let q1 = IntQueue.enq q0 1
let q2 = IntQueue.enq q1 2
let (_,q3) = IntQueue.deq q2
let _ = IntQueue.print q1
let _ = IntQueue.print q2
let _ = IntQueue.print q3
创建了一个队列,添加和删除了元素,然后输出了其状态。输出结果如下所示:
1
1 2
2
.
到目前为止,我们已经介绍了 OCaml 的基本特性。
掌握了这些内容后,编写基本程序应该不会有太大困难。
接下来的两节将学习函数式编程中常用的两种编程风格 —— 递归函数和高阶函数。
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2022.9.14
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 - R. Neapolitan, Foundations of Algorithms (5th ed.), Jones & Bartlett, 2015. - T. Cormen《算法导论》(第三版),麻省理工学院出版社,2009年。 - T. Roughgarden, Algorithms Illuminated, Part 1~3, Soundlikeyourself Publishing, 2018. - J. Kleinberg&E. Tardos, Algorithm Design, Addison Wesley, 2005. - R. Sedgewick&K. Wayne,《算法》(第四版),Addison-Wesley,2011 - S. Dasgupta,《算法》,McGraw-Hill教育出版社,2006。 - S. Baase&A. Van Gelder, Computer Algorithms: 设计与分析简介》,Addison Wesley,2000。 - E. Horowitz,《C语言中的数据结构基础》,计算机科学出版社,1993 - S. Skiena, The Algorithm Design Manual (2nd ed.), Springer, 2008. - A. Aho, J. Hopcroft, and J. Ullman, Design and Analysis of Algorithms, Addison-Wesley, 1974. - M. Weiss, Data Structure and Algorithm Analysis in C (2nd ed.), Pearson, 1997. - A. Levitin, Introduction to the Design and Analysis of Algorithms, Addison Wesley, 2003. - A. Aho, J. Hopcroft, and J. Ullman, Data Structures and Algorithms, Addison-Wesley, 1983. - E. Horowitz, S. Sahni and S. Rajasekaran, Computer Algorithms/C++, Computer Science Press, 1997. - R. Sedgewick, Algorithms in C: 第1-4部分(第三版),Addison-Wesley,1998 - R. Sedgewick,《C语言中的算法》。第5部分(第3版),Addison-Wesley,2002 |