函數式編程之-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中的好處是顯而易見的。
選項類型中,彷佛把三種不一樣的支付方式揉在了一塊,那麼每種支付方式的支付過程這種行爲怎麼實現呢?答案是模式匹配,咱們將在下節介紹。

相關文章
相關標籤/搜索