在深入到函数式编程思想之前, 了解函数式独有的类型是非常有必要的. 函数式类型跟 OO 语言中的数据结构截然不同, 这也导致使用函数式编程语言来解决问题的思路跟 OO 的思路有明显的区别.
什么是类型? 类型在编程语言中有什么作用呢? 一般来说, 类型有两个作用:
首先当你对某个数据声明类型后, 就拥有了编译时的检查, 换句话说, 你可以认为类型充当了 "编译时的单元测试";
类型系统可以让你建立一种模型, 用来表达真实世界中的模型;
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. 这样的模型定义犯了三个错误:
没有把相关一组类型组合起来, 例如 FirstName, MiddleName, LastName. 这三个类型共同组成了 Name, 我们的模型并没有体现出这样的设计.
EmailAddress 真的是一个 string 吗? 他能有效的表达 Email 这样的 Domain 吗? Email 分有效和无效, 他拥有自己的规则, 并不是所有的字符串都是 Email,string 这样的类型无法表达 Email 的 Domain 含义.
在 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 中的好处是显而易见的.
在选项类型中, 似乎把三种不同的支付方式揉在了一块, 那么每种支付方式的支付过程这种行为怎么实现呢? 答案是模式匹配, 我们将在下节介绍.
来源: https://www.cnblogs.com/xiandnc/p/9382139.html