当你想把数据组成一个结构化的格式,而不需要太复杂的语法时,你可以使用F#中的Record类型。Record类型与C语言的Struct类型基本一样,存储一组类型的值,通过字段的值来获取。定义一个Record类型很简单,只需要在大括号内定义系列的名称/类型就可以。要实例化一个Record,只需要提供对应的字段以及值即可,剩下的类型推断系统会根据你的输入来自动判断,比如:
> type PersonRec = {First : string; Last : string; Age : int};;
type PersonRec =
{First: string;
Last: string;
Age: int;}
> let steve = {First = "Steve"; Last="Holt"; Age=21};;
val steve : PersonRec = {First = "Steve";
Last = "Holt";
Age = 21;}
> printfn "%s is %d years old" steve.First steve.Age;;
Steve is 21 years old
val it : unit = ()
Cloning Records
利用关键字"with"可以很容易的实现记录克隆,比如上面的那个steve有一个双胞胎哥哥,可以用下面的代码来实例化他哥哥,^_^:
> let bill = {steve with First="Bill"};;
val bill : PersonRec = {First = "Bill";
Last = "Holt";
Age = 21;}
如果克隆时需要更改多个字段的值,只需要用逗号分隔开来。
Pattern Matching
Record也可用于模式匹配,使用时只需要提供Record的字段值与给定的字面值都匹配即可,不如给定一辆车,将车的型号为品牌做匹配:
> type Car = {Model : string; Color : string};;
type Car =
{Model: string;
Color: string;}
> let matchCar newCars =
- newCars
- |> List.filter
- (function
- | {Model = "Coup" } -> true
- | {Model = "BMW" } -> true
- | _ -> false);;
val matchCar : Car list -> Car list
> let cars = [{Model = "Coup" ; Color = "Red"};{Model = "Benz" ; Color = "Black"
- }];;
val cars : Car list = [{Model = "Coup";
Color = "Red";}; {Model = "Benz";
Color = "Black";}]
> matchCar cars;;
val it : Car list = [{Model = "Coup";
Color = "Red";}]
Type Inference
在F#中,类型推断系统是一个非常重要的东西,几乎所有的地方都需要用到它,现在来看看Record是在类型推断系统下工作的。在.NET中,类在使用前,必须标明它的类型,而在F#中,判断Record类型是根据Record的字段来的,就像我们上面的说,利用let绑定到一个大括号包围的数据,类型推断系统就能根据它的字段来推理出是Record。就拿下面的代码段来说,对于值pt1和pt2,没有任何地方标明它是什么类型,但是由于X和Y值被访问,并且F#编译器知道有一个Record包含有字段X和Y,自然就推断出pt1和pt2的类型了:
> type Point = {X : float; Y : float};;
type Point =
{X: float;
Y: float;}
> let distane pt1 pt2 =
- let square x = x * x
- sqrt <| square (pt1.X - pt2.X) + square (pt1.Y - pt2.Y);;
val distane : Point -> Point -> float
> distane { X = 0.0; Y = 0.0} {X = 10.0 ; Y = 10.0};;
val it : float = 14.14213562
当两个Record的具有相同的字段时,如果像上面这样写,那么类型推断系统就会发出一个错误了,因为它不知道你希望用到的是哪个类型。为了解决这个问题,你可以提供类型注释或者完整路径的字段,比如:
> type Point = {X : float; Y : float};;
type Point =
{X: float;
Y: float;}
> type Vector3 = {X : float; Y : float; Z : float};;
type Vector3 =
{X: float;
Y: float;
Z: float;}
> let distance (pt1 : Point) (pt2 : Point) =
- let square x = x * x
- sqrt <| square (pt1.X - pt2.X) + square (pt1.Y - pt2.Y);;
val distance : Point -> Point -> float
引入字段的完整路径名可以像下面这样子:
> let origin = { Point.X = 0.0; Point.Y = 0.0 };;
val origin : Point = {X = 0.0;
Y = 0.0;}
Methods and Properties
在Record中也可以增加方法和属性:
> type Vector =
- {X : float; Y : float; Z : float}
- member this.Length =
- sqrt <| this.X ** 2.0 + this.Y ** 2.0 + this.Z ** 2.0;;
type Vector =
{X: float;
Y: float;
Z: float;}
with
member Length : float
end
> let v = {X = 10.0; Y = 20.0; Z = 30.0};;
val v : Vector = {X = 10.0;
Y = 20.0;
Z = 30.0;}
> v.Length;;
val it : float = 37.41657387