定义类型
F#自定义类型有2种。
  • 元组或者记录,即组合多个不同的类型。类似C的结构或者C#中的类。
  • 联合类型。
元组和记录类型(Tuple and Record Types)
元组就是用逗号,把几个值放在一起。可以把元组赋值给一个标识符,也可以反过来。如果需要忽略元组中的一个值,可以用'_'告诉编译器,忽略该值,如下面的代码。
let  pair   =   true,   false
let  b1, _   =  pair
let  _, b2   =  pair
元组不需要用type关键字显式定义。通常定义一个类型,需要用到type关键字,然后是类型名=,用这种方法你可以给一个已经存在的类型起一个别名,特别当你想要是有元组作为类型约束的时候,这招很有用。看下面的例子:
type  Name   =   string
type  Fullname   =   string   *   string
let  fullNameToSting   (x: Fullname )   =
     let  first, second   =  x   in
        first   +   " "   +  second

记录类型和元组类型类似,在一个类型中组合了多个类型。不同点在于纪录类型中,每个字段都是有名字的。
 
字段定义在括号里,用分号分割。Organization1是一个记录类型,它的字段名是唯一的。这意味着,不需要写出Organization1这个类型,也可以用简便语法创建Organization1的实例,只需要字段签名都一致。
#light
// 定义organization,使用唯一的字段
type  Organization1  =   {  boss: string ;  lackeys:  string  list  }
// 创建organization实例。没有显式的提及Organization1类,只靠字段一致,编译器字段推断
let  rainbow  =
     {  boss  =   "Jeffrey" ;
    lackeys  =   [ "Zippy" ;   "George" ;   "Bungle" ]   }
// 定义2个organizations,有重复的字段名字
type  Organization2  =   {  chief: string ;  underlings: list < string >   }
type  Organization3  =   {  chief: string ;  indians:  string  list  }
// 创建Organization2实例
let   ( thePlayers: Organization2 )   =
     {  chief  =   "Peter Quince" ;
    underlings  =   [ "Francis Flute" ;   "Robin Starveling" ;
     "Tom Snout" ;   "Snug" ;   "Nick Bottom" ]   }
// 创建Organization3实例
let   ( wayneManor: Organization3 )   =
     {  chief  =   "Batman" ;
    indians  =   [ "Robin" ;   "Alfred" ]   }
F#不强制字段名必须唯一。因此编译器有时候就无法仅仅靠字段推断类型,这时就要用类型标注。
访问类型字段也很简单,用.号就可以了。
#light
// define an organization type
type  Organization   =   {  chief: string ;  indians:   string  list   }
// create an instance of this type
let  wayneManor   =
     {  chief   =   "Batman" ;
    indians   =   [ "Robin" ;   "Alfred" ]   }
// access a field from this type
printfn   "wayneManor.chief = %s"  wayneManor. chief
 
Record类型默认是不可变的。对一个习惯于用命令式语言开发的程序来说,听起来record类型没什么用,因为某些地方要不可避免的要改变字段值。出于这个目的,F#提供一个简便语法,通过更新字段创建record的一个副本。要创建这样一个副本,只需要把record类型的名字放在括号中,后面跟with关键字。看下面的例子。
#light
// define an organization type
type  Organization   =   {  chief: string ;  indians:   string  list   }
// create an instance of this type
let  wayneManor   =
     {  chief   =   "Batman" ;
    indians   =   [ "Robin" ;   "Alfred" ]   }
// create a modified instance of this type
let  wayneManor ' =
    { wayneManor with indians = [ "Alfred" ] }
// print out the two organizations
printfn "wayneManor = %A" wayneManor
printfn "wayneManor'
  =   %A " wayneManor'
上述例子创建了wayneManor',把Robin值移除了。结果如下:
3fc8d98dbe424266983bea25cbe29897

 
另一种访问record类型字段的方法是使用模式匹配。看例子:
#light
// type representing a couple
type  Couple   =   {  him :   string   ;  her :   string   }
// list of couples
let  couples   =
     [   {  him   =   "Brad"   ;  her   =   "Angelina"   } ;
     {  him   =   "Becks"   ;  her   =   "Posh"   } ;
     {  him   =   "Chris"   ;  her   =   "Gwyneth"   } ;
     {  him   =   "Michael"   ;  her   =   "Catherine"   }   ]
// function to find "David" from a list of couples
let   rec  findDavid l   =
     match  l   with
    |   {  him   =  x   ;  her   =   "Posh"   }  :: tail   ->  x
    | _ :: tail   ->  findDavid tail
    |   [ ]   ->   failwith   "Couldn't find David"
// print the results
printfn   "%A"   (findDavid couples )
输出:Becks
 
联合类型
 
联合类型是把不同意义,不同结构的数据放在一起的一种手段。定义联合类型,使用type关键字,然后是名字,然后是=。接下来是不同constructor的定义,用竖线|分割,第一个竖线是可选的。 constructor中名称的一个字母必须大写,使得避免constructor名字和标识符名混起来。名称可以选择跟着一个关键字of,这就构成了constructor。多个类型构成的constructor要用*分割。类型中,constructor的名字必须是唯一的。如果定义了几个联合类型,constructor的名字可以重复。但最好不要这样做,因为这样在某些场合要求类型注解。

 
下例定义了一个Volume类型,它的值有3个不同的意义,liter,US品脱,或者英制品脱,尽管数据的结构都是相同的,都是float,意义却都不同。把数据的意义混合在一个算法里,是常见的导致程序产生bug的原因。Volume类型,某种程度上是要避免这种问题。
 
#light
type  Volume   =
| Liter   of   float
| UsPint   of   float
| ImperialPint   of   float
let  vol1   =  Liter   2.5
let  vol2   =  UsPint   2.5
let  vol3   =  ImperialPint   ( 2.5 )

 
创建联合类型实例的语法,就是constructor名字,然后就是类型的值,多个值的话就逗号分割。你也可以把值放在括号里。
 
要拆解联合类型的基础部分,需要使用模式匹配。当对一个联合类型模式匹配时,constructor构成了模式匹配的一半规则。关于联合类型可以看:
http://en.wikipedia.org/wiki/Union_(computer_science)
http://cnn237111.blog.51cto.com/2359144/884576
 
具体使用,看下面的例子就能明白了。
#light
// type representing volumes
type  Volume   =
| Liter   of   float
| UsPint   of   float
| ImperialPint   of   float
// various kinds of volumes
let  vol1   =  Liter   2.5
let  vol2   =  UsPint   2.5
let  vol3   =  ImperialPint   2.5

// some functions to convert between volumes
let  convertVolumeToLiter x   =
     match  x   with
        | Liter x   ->  x
        | UsPint x   ->  x   *   0.473
        | ImperialPint x   ->  x   *   0.568
let  convertVolumeUsPint x   =
     match  x   with
    | Liter x   ->  x   *   2.113
    | UsPint x   ->  x
    | ImperialPint x   ->  x   *   1.201

let  convertVolumeImperialPint x   =
     match  x   with
    | Liter x   ->  x   *   1.760
    | UsPint x   ->  x   *   0.833
    | ImperialPint x   ->  x
// a function to print a volume
let  printVolumes x   =
    printfn   "Volume in liters = %f,
        in us pints = %f,
        in imperial pints = %f"

         (convertVolumeToLiter x )
         (convertVolumeUsPint x )
         (convertVolumeImperialPint x )
// print the results
printVolumes vol1
printVolumes vol2
printVolumes vol3
 
运行结果如下:
b4f5940e121f4e5ca6897d52cb867ccb

  使用类型参数定义类型
参数化类型意味着在类型的定义内部空出一些类型,在后期由类型使用者确定具体类型。
F#支持2种类型参数化的语法。第一种,把参数化的类型放在type和类型名之间,如下:
 
#light
type   'a BinaryTree =
    | BinaryNode of '
a BinaryTree   *   'a BinaryTree
    | BinaryValue of '
a
let  tree1   =
        BinaryNode (
            BinaryNode   (  BinaryValue   1, BinaryValue   2 ),
            BinaryNode   (  BinaryValue   3, BinaryValue   4 )   )
第二种是在类型名字后加上尖括号,如下:
#light type Tree<'a> = | Node of Tree<'a> list | Value of 'a let tree2 = Node ( [ Node ( [Value "one"; Value "two" ] ) ; Node ( [Value "three"; Value "four" ] ) ] )
和变量类型相同,参数的名字都是由单引号(')开头。通常都使用单个字母作为类型名字。如果需要多个参数类型化,你必须用逗号分割。
上述例子BinaryTree类型采用OCaml语法风格,tree类型采用.NET分格。使用哪种语法不影响编译器做类型推断。看一下
看下面的tree1和tree2的例子。
 
#light
// 定义二叉树
type 'a BinaryTree =
| BinaryNode of 'a BinaryTree * 'a BinaryTree
| BinaryValue of 'a
// 创建二叉树实例
let tree1 =
    BinaryNode(
        BinaryNode ( BinaryValue 1, BinaryValue 2),
        BinaryNode ( BinaryValue 3, BinaryValue 4) )

// 定义一棵树
type Tree<'a> =
| Node of Tree<'a> list
| Value of 'a
// 创建一棵树
let tree2 =
    Node( [ Node( [Value "one"; Value "two"] ) ;
        Node( [Value "three"; Value "four"] ) ] )

// 打印二叉树的递归函数
let rec printBinaryTreeValues x =
    match x with
    | BinaryNode (node1, node2) ->
    printBinaryTreeValues node1
    printBinaryTreeValues node2
    | BinaryValue x -> printf "%A, " x

// 打印树的递归函数
let rec printTreeValues x =
    match x with
    | Node l -> List.iter printTreeValues l
    | Value x -> printf "%A, " x

// 打印结果
printBinaryTreeValues tree1
printfn ""
printTreeValues tree2
递归的定义类型
F#中,类型只能按照先后顺序引用,比如A类型写在前面,B类型在后面,那么A类型引用B类型就会出错。比如:
type A={
        b:B;
   
}

type B={
        name:string;
   
}
那么就会出错:
 
68c77d84beaf421187ae85408e96fff3
那么解决方法就是把B的定义放到A的前面。
但是如果类型要递归定义的话,比如A引用B,B同时引用了A,那么A和B谁写在前面,都会出现无法未定义类型的错误。
type B={
        name:string;
        value:A;
    }

type A={
        b:B;
    }
出现错误:
9b9a5dc79d0d43a3b08e2df578c97279
如果把A写在B前面
type A={
        b:B;
    }
type B={
        name:string;
        value:A;
    }

出现错误:
68c77d84beaf421187ae85408e96fff3
F#提供专用的语法,递归的定义类型。就是使用and关键字。该类型必须声明在一起,在一个块内。块之间不能有其他赋值定义。
type A={
        b:B;
    }
and B={
        name:string;
        value:A;
    }

这种方法定义的类型不同于常规方法定义的类型。这种类型可以引用块内的其他类型,甚至可以互相引用。下面的例子,就是两个类型互相引用。XmlElement 和XmlTree声明在同一个块内,如果他们分开定义,XmlElement不能够引用XmlTree,因为XmlElement在XmlTree声明了。正式由于它们的定义由and关键字连接,XmlElement类可以有个XmlTree类型的字段。

// represents an XML attribute
type XmlAttribute =
    { AttribName: string;
    AttribValue: string; }
// represents an XML element
type XmlElement =
    { ElementName: string;
    Attributes: list<XmlAttribute>;
    InnerXml: XmlTree }
// represents an XML tree

and XmlTree =
| Element of XmlElement
| ElementList of list<XmlTree>
| Text of string
| Comment of string
| DDD of XmlAttribute
| Empty