Chapter3 - Defining Types - Record
时间:2010-09-27 来源:兴说:
一种叫 Tuple 或者是 Records,他们跟 C 的 Struct 与 C# 的 Class 很相似。
另一种则是 sum 类型,有时他们也指 Union 类型。
下面我们先讲 Record
Tuple 是一种快速且简易的方式帮助我们将各个值封装到一个组里。而且也提供了方法让我们将 Tuple
里的值单独取出。看一个简单的例子;
#light
let pair = true, false
let b1, _ = pair
let _, b2 = pair
在这里,我们使用下划线 _ 来表示忽略某个值,比如 b1, _ = pair 则表示我们只需要第一个值,此时则忽略了
第二个值,后面的表达式也是同一个意思。
而我们也可以使用关键字 type 来定义另一种类型。比如为一个 tuple 取别名,下例:
#light
type Name = string
type Fullname = string * string
let fullNameToString (x : Fullname) =
let first, second = x in
first + " " + second
上例表示了传递一个 Fullname 类型,并将其转化为 string 的方法。而 Fullname 实际上就是一个 Tuple
Record 类型有点类似于 Tuple 可用于将多个类型的值绑定到一个别名或一个组,而他们的区别则主要
在于,Record 可以为绑定的值命名。下面看一个例子
#light
type Organization1 = { boss : string; lackeys : string list }
let rainbow =
{ boss = "Jeffrey";
lackeys = ["Zippy"; "George"; "Bungle"] }
type Organization2 = { chief : string ; underlings : string list }
type Organization3 = { chies : string ; indians : string list }
let thePlayers =
{ new Organization2
with chief = "Peter Quince"
and underlings = [ "Francis Flute"; "Robin Starveling";
"Tom Snout"; "Snug"; "Nick Bottom"] }
let wayneManor =
{ new Organization3
with chief = "Batman"
and indians = ["Robin"; "Alfred"] }
F# 并没有强制 Record 里的命名必须是唯一的,所以当编译器无法推断出类型的时候,你最好还是加上类型声明。
而当如果你定义的类型里面需要使用到你下面即将定义的另外一个类型,
你可以使用 and 关键字来告诉编译器。看例子
#light
type recipe =
{ recipeName : string ;
ingredients : ingreddient list;
instructions : string }
and ingredient =
{ ingredientName : string ;
quantity : int }
let greenBeansPineNuts =
{ recipeName = "Green Beans & Pine Nuts" ;
ingredients =
[ { ingredientName = "Green beans"; quantity = 250 };
{ ingredientName = "Pine nuts"; quantity = 250 }] //后面还有很多,略
instructions = "Parboil the green beans for about minutes ........."}
let name = greenBeansPineNuts.recipeName
let toBuy =
List.fold //原书为 fold_left 在 F# 2.0 中已取消
(fun acc x ->
acc +
(Printf.sprintf "\t%s - %i\r\n" x.ingredientName x.quantity) )
"" greenBeansPineNuts.ingredients
let instructions = greenBeansPineNuts.instructions
printf "%s\r\n%s\r\n\r\n\t%s" name toBue instructions
这里应该都没啥问题,唯一需要解释的可能是 fold函数。我们来看看他的定义
val it : (('a -> 'b -> 'a) -> 'a -> 'b list -> 'a) = <fun:clo@42>
参数的定义我就不解释了,免得越说越乱。他其实就是使用传递的第一个参数: 一个匿名函数
对第三个 参数 : 一个 b 类型的列表进行遍历,进行处理,然后第二个就是一个状态值,他用来保存
每次执行 匿名函数后的结果。
所以像上面的 toBuy 函数执行的结果其实就是将 ingredients 的每个元素按一定的规则转化为字符串,
并连接起来。
下面我们来看一个 Record 的模式匹配。
#light
type couple = { him : string; her : string }
let couples =
[ { him = "Brad"; her = "Angelina" };
{ him = "Becks"; her = "Posh" };
{ him = "Chris"; her = "Gwyneth" };
{ him = "Michael" her = "Catherine" } ]
let rec findDivid l =
match l with
| { him = x ; her = "Posh" } :: tail -> x
| _ :: tail -> findDavid tail
| [] -> failwith "couldn't find David"
printf findDavid couples
上面的就比较简单易懂了,定义了一个 夫妻 (couple) 类型,里面有 丈夫 (him) 跟 妻子 (her) 两个字段,
然后定义了一个 夫妇 (couple)的列表, 接着的一个函数则在这个列表中查找第一个 妻子 (her) 名为
Posh 的夫妇,找到后把丈夫的名字赋予标识符 x ,然后返回 x, 如果没找到则抛出找不到的异常。