函数式编程之-F#类型系统

在深刻到函数式编程思想以前,了解函数式独有的类型是很是有必要的。函数式类型跟OO语言中的数据结构大相径庭,这也致使使用函数式编程语言来解决问题的思路跟OO的思路有明显的区别。编程

什么是类型?类型在编程语言中有什么做用呢?通常来讲,类型有两个做用:数据结构

  1. 首先当你对某个数据声明类型后,就拥有了编译时的检查,换句话说,你能够认为类型充当了“编译时的单元测试”;
  2. 类型系统可让你创建一种模型,用来表达真实世界中的模型;

Tuple type

元组是函数式编程语言中的经常使用类型,同时在.NET 4.0中被引入到了C#中。可是在C#中几乎不太会用到这种类型,可是在函数式编程语言中却随处可见。
例如在C#中这样使用元组:编程语言

var s = new Tuple<int, int>(1, 2);
var fist = s.Item1;

在F#中函数式编程

let t = 1,2
let first = fst t //提取第一个元素
let second =snd t //提取第二个元素

let f s = t //经过解构提取元素

Record type

Record type是F#中最经常使用的类型。常常被用来对领域建模(Domain modeling)。例如定义一个矩形数据结构:函数

type Rect = {
    Left: float32
    Top: float32
    Width: float32
    Height: float32
}

这看起来跟OO语言中对class的定义是很类似的,比如在某个class中声明了一组属性。使用起来也简单:单元测试

let rc = {
    Left = 10.0f; 
    Top = 10.0f;
    Width = 200.0f; 
    Height = 200.0f
}

你能够把它当作一个简单的class,可是从定义和使用都能看出来Record type更加简单和直接一些。而且咱们并无在Record type里设计一些方法,这跟class有本质的区别。
Record type还支持“复制一个现有记录并进行一些修改”:测试

let rc2 ={ rc with Left = rc.Left + 100.0f }

C#中的class是没有这种能力的,你不得不显示复制全部属性。
另外Record type自动实现了equal操做符:翻译

type Name = { First:string ; Last:string}

let jim = { First ="Jim"; Last = "Dan"}
let jim2 = {First =  "Jim"; Last = "Dan"}
let isSame = jim = jim2  //true

使用Record type来创建领域模型

考虑下面的Contact领域模型:设计

type Contact = {
    FirstName: string;
    MiddleName: string;
    LastName: string;
    EmailAddress: string;
    Address1: string;
    Address2: string;
    City: string;
    State: string;
    Zip: string;
}

若是你把它当作一个class也是可行的,实际上在OO语言里咱们也常常设计这样的class。这样的模型定义犯了三个错误:code

  1. 没有把相关一组类型组合起来,例如FirstName, MiddleName, LastName。这三个类型共同组成了Name,咱们的模型并无体现出这样的设计。
  2. EmailAddress真的是一个string吗?他能有效的表达Email这样的Domain吗?Email分有效和无效,他拥有本身的规则,并非全部的字符串都是Email,string这样的类型没法表达Email的Domain含义。
  3. 在F#中没有null,若是你认为某个类型可能为空,就应该设计为option类型,例如MiddleName,他应该是string option。
    还记得前面的章节咱们说函数式编程的核心思想是组合,组合不但体如今函数之间的组合,类型也是可组合的:
type PersonalName = {
    FirstName: string;
    MiddleName: string option;
    LastName: string;
}

type EmailContactInfo = {
    EmailAddress: string;
    IsEmailVerified: bool;
}

type PostalAddress = {
    Address1: string;
    Address2: string;
    City: string;
    State: string;
    Zip: string;
}

type PostalContactInfo = {
    Address: PostalAddress;
    IsAddressValid: bool;
}

type Contact = {
    Name: PersonalName;
    EmailContactInfo: EmailContactInfo;
    PostalContactInfo: PostalContactInfo;
}

Descriminated Unions type

中文翻译过来叫作可区分联合,这种类型试图为不一样的选项进行建模,因此你能够把他理解为选项类型
举个例子:“如今温度是多少?“
如何对如今的温度建模?你问的是摄氏度呢仍是华氏度呢?若是是摄氏度即38°,若是单位是华氏度,则为100.4°。

type Temperature = 
    | F of float
    | C of int 

let tempNow = C(30)
let tempNow2 = F(100.4)

只有一个选项的类型:

type EmailAddress = EmailAddress of string
let email = "a" |> EmailAddress
let emails = ["a"; "b"; "c"] |> List.map EmailAddress

使用 Descriminated Unions type来创建领域模型

选项类型在F#是很是经常使用的领域模型建模类型,好比设计一个关于支付的模型,在OO语言中,你可能会这样作:

interface IPaymentMethod { }

class Cash : IPaymentMethod { }

class Cheque: IPaymentMethod { }

class Card : IPaymentMethod { }
...

在函数式语言中利用选项类型能够轻松搞定:

type PaymentMethod =
   | Cash
   | Cheque of ChequeNumber
   | Card of CardType * CardNumber

OO思想中经过抽象接口和定义派生类来实现这个模型。函数式语言则利用选项类型把模型核心内容经过尽量少的代码展示出来。
此时你也许会有所疑虑,在OO的设计中,每种支付方式都是一个独立的实现,因此每种支付方式的具体行为就能够设计在具体的实现中,例如Payment的过程。不一样的支付方式显然须要不一样的支付过程,这种设计在OO中的好处是显而易见的。
选项类型中,彷佛把三种不一样的支付方式揉在了一块,那么每种支付方式的支付过程这种行为怎么实现呢?答案是模式匹配,咱们将在下节介绍。

相关文章
相关标签/搜索