内容简介:將實質上不同的型別,在邏輯上看成相同的型別。如 function 可能回傳若說將不同的型別,整合成單一型別。
將實質上不同的型別,在邏輯上看成相同的型別。如 function 可能回傳 int
或 bool
兩種型別,可為此 function 特別建立 IntOrBool
型別,同時包含 int
與 bool
,這就是 Discriminated Union,簡稱 union
。
若說 tuple
是將不同型別加以 AND,則 union
是將不同型別加以 OR。
Definition
將不同的型別,整合成單一型別。
Q : 為什麼要稱為 Discriminated
Union?
因為不是簡單的將不同型別加以 union 而已,而可以將不同的型別取 case-identifier
加以區別 (discriminated),所以稱為 Discriminated
Union。
Syntax
[ attributes ]
type type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
...
-
type-name
: 定義
union的型別名稱 -
I
: 因為
union包含多種型別,所以使用|,第一個|可省略 -
case-identifider
: 為每個型別取一個別名,必須為
大駝峰 - of : 每個別名的型別,可以是內建型別,也可以是自己定義的其他 type
-
*
: 若
of之後為tuple,不同 type 以*區別不同型別 -
fieldname
: 若
of型別是tuple,可為tuple內每個型別取別名
以上只有 type-name
與 case-identifier
為必須,其他都可省略。
簡單來說, |
之後稱為 case, of
之後稱為 field
Case 不可省略,但 field 可省略
Case
Case of Primitive Type
type OrderId = | Int of int | Bool of bool
如 function 找得到資料會傳回 int
型態的的 orderId
,若找不到則傳回 bool
型態的 false
,也就是回傳型態可能是 int
或 bool
,可將此型態重新定義為 OrderId
union,則無論傳回 int
或 bool
都是 OrderId
union,且也只能傳回 int
或 bool
。
type OrderId = Int of int | Bool of bool
若 case 很少,也可以寫成一行,則第一個 |
可省略。
Case of Unnamed Type
type Shape =
| Rectangle of int * int
| Circle of int
| Prism of int * int * int
of
之後的型別,如是 unnamed type,可以直接 inline 表示,如直接指定為 tuple
。
如打算將 Rectangle
、 Circle
與 Prism
三個型別定義出一個新的 Shape
union,只要是 Rectangle
或 Circle
或 Prism
之一,都算是 Shape
。
-
Rectangle為int*int組合的tuple -
Circle為int -
Prism為intintint組合的tuple
type * type 為 tuple 的型別定義方式
type MixedType =
Tuple of int * int
List of int list
Collection
也屬於 unnamed type,亦可直接 inline 表示。
int list
表示為 list
型別,其 element 型別為 int
。
let rectangle = Rectangle (1, 2) let circle = Circle 1 let prism = Prism (1, 2, 3)
當建立 union
時,以類似 constructor 的方式建立,稱為 case constructor,唯沒有 new
, class
換成 case
,且必須要照 定義順序
傳入。
type C =
| Circle of int
| Rectangle of int * int
[1..10]
|> List.map Circle
[1..10]
|> List.zip [21..30]
|> List.map Rectangle
Case contructor 本質就是 function,因此任何可傳入 function 之處,就可傳入 case constructor。
Case of Named Type
type Person = { first: string; last: string }
type IntOrBool = Int of int | Bool of bool
type MixedType =
| Person of Person
| IntOrBool of IntOrBool
type MixedType =
| Person of { first: string; last: string } // error
| IntOrBool of (Int of int | Bool of bool) // error
of
之後的型別若是 named type,則必須先用 type
定義好型別,如 record
或 union
,不能以 inline 的方式表示。
Field
若 case 的型別為 tuple
,雖能在 of
之後簡單的宣告 int * int
,有幾個缺點 :
-
要建立
union時,只能依照定義順序傳入,可讀性較差 -
無法由
tuple看出其 domain 上的意義
若我們加上 field,則清楚許多。
type Shape =
| Rectangle of width : int * length : int
| Circle of radius: int
| Prism of width: int * length : int * height: int
在 of
之後加上 field,可明確表達出 tuple
的每個 element 的 domain 意義。
let rectangle = Rectangle (length = 1, width = 2) let circle = Circle (radius = 1) let prism = Prism (width = 1, length = 2, height = 3)
建立 union
時,可在 case constructor 明確指定其 field,如此可讀性更高,且不用依照 定義順序
傳入。
Empty Case
type Directory =
| Root
| Subdirectory of string
type Result =
| Success
| ErrorMessage of string
Case 並不一定要搭配 type,若該 case 並不需要任何型態的值傳入,可以不指定 type。
let myDir1 = Root let myDir2 = Subdirectory "bin" let myResult1 = Success let myResult2 = ErrorMessage "not found"
沒有 type 的 case,其 case constructor 就不用傳入任何值。
type Size = Small | Medium | Large let mySize = Small
當全部 case 都沒有 type 時,其功能等效於 enum
。
type Size = Small | Medium | Large // DU type Size = Small = 0 | Medium = 1 | Large = 2 // enum
union
與 enum
都使用 type 定義,沒有指定 int
值為 union
,有則為 enum
。
Q : F# 也有 enum
,我該用 union
還是 enum
呢 ?
F# 的 union
功能較強, enum
只是 union
的特例,實務上應優先使用 union
,除非有以下需求:
-
Case 必須搭配
int -
union必須與其他 .NET 語言搭配時
才必須使用 enum
。
F# 的 enum
與 .NET 的 enum
是相同的
Single Case
雖然 union
原本的用途是用在將不同的型別整合成單一型別,也就是將不同的 case 整合成一個 union
,但實務上有一種應用是一個 union
只有一個 case,所謂的 single case。
type CustomerId = int type OrderId = int let printOrderId (orderId: OrderId) = printfn "The orderId is %i" orderId let custId = 1 printOrderId custId
第 1 行
type CustomerId = int type OrderId = int
type
能對 primitive type 取 alias,所以我們分別對 int
定義成 CustomerId
type 與 OrderId
type。
第 4 行
let printOrderId (orderId: OrderId) = printfn "The orderId is %i" orderId
建立 printOrderId
function,傳入參數的型別為 OrderId
。
第 7 行
let custId = 1 printOrderId custId
custId
的型別為 int
,傳入 printOrderId
compiler 也沒報錯,明明要的是 OrderId
型別。
因為 OrderId
與 CustomerId
都只能算是 int
的 alias,還不算是個型別。
type CustomerId = CustomerId of int type OrderId = OrderId of int let printOrderId (OrderId orderId) = printfn "The orderId is %i" orderId let custId = CustomerId 1 printOrderId custId // Error
第 1 行
type CustomerId = CustomerId of int type OrderId = OrderId of int
-
定義
CustomerIdunion,其 case 為CustomerId,型別為int -
定義
OrderIdunion,其 case 為OrderId,型別為int
當使用 single case 的 union
時,type 會與 case 相同
第 4 行
let printOrderId (OrderId orderId) = printfn "The orderId is %i" orderId
建立 printOrderId
function,傳入參數的型別為 OrderId
。
與之前的 printOrderId
function 一樣。
第 7 行
let custId = CustomerId 1 printOrderId custId // Error
custId
型別不再是 int
,而是 CustomerId
,因為使用了 CustomerId
的 case constructor 建立。
custId
傳入 printOrderId
後,如願出現 compiler error,因為 OrderId
與 CustomerId
都是具體的 type,而不只是 alias。
Destructor
let getShapeHeight shape =
match shape with
| Rectangle(height = h) -> h
| Circle(radius = r) -> 2. * r
| Prism(height = h) -> h
當 union
傳入 function 後,可使用 Pattern Matching 與 field 將 tuple
的值取出。
with
之後配合的 union
的 case, ()
內配合 field,可以直接取出該 field 的值。
使用 field 之後,可輕易的配合 Pattern Matching 取出 tuple
內的值
let getCustomerId (CustomerId customerId) =
printfn "The CustomerId is %i" customerId
在 function 的 paramter 使用 ()
,將 case 寫在 parameter 之前,則自動會將傳入的 union
destruct 成 value。
語法雖然很類似 C#,但別忘了 F# 的 type 是在 :
之後,所以 CustomerId
寫在前面並不是型別,而是 union
的 case
let (CustomerId customerId) = custId let CustomerId customerIdInt = custId // error
custId
為 CustomerId
union,會直接 destruct 成 customerId
。
使用 destructor 時,一定要加上 ()
,否則會誤以為是新的 function
Equality
type Contact =
| Email of string
| Phone of int
let email1 = Email "bob@example.com"
let email2 = Email "bob@example.com"
let areEqual = (email1=email2) // true
雖然 union
為 reference type,但 union
的比較卻像 value type,只要 type 一樣,value 一樣, union
就算一樣。
Representation
type Contact = Email of string | Phone of int let email = Email "bob@example.com" printfn "%A" email // nice
printfn
使用 %A
支援 union
。
Object Hierarchy
type Shape =
| Circle of float
| EquilateralTriangle of double
| Square of double
| Rectangle of double * double
若使用 OOP,會設計 Shape
interface,再由 Circle
、 EquilateralTriangle
、 Square
與 Rectangle
實踐 Shape
,如此需要開 5 個檔案。
若使用 union
,只要 5 行就可解決。
let pi = 3.141592654
let area myShape =
match myShape with
| Circle radius -> pi * radius * radius
| EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
| Square s -> s * s
| Rectangle (h, w) -> h * w
若使用 OOP,由於各種形狀計算面積的公式不同,勢必在 Shape
interface 開 area()
,再由 Circle
、 EquilateralTriangle
、 Square
與 Rectangle
各自實作 area()
。
但在 FP 的 F#,只需使用 pattern matching 根據 union
的不同 case 實作即可,6 行即可解決。
Conclusion
-
F# 的
union非常強大,可以算是enum的威力加強版,搭配 Pattern Matching 更是如虎添翼 -
union配合tuple可以定義出複雜的 domain model -
Single case 的
union可以替 domain 定義一個更有意義的型別名稱,且兼具 type safety 與 compiler 保護
Reference
以上所述就是小编给大家介绍的《深入探討 F# 之 Discriminated Union》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【1】JavaScript 基础深入——数据类型深入理解与总结
- 深入理解 Java 函数式编程,第 5 部分: 深入解析 Monad
- 深入理解 HTTPS
- 深入理解 HTTPS
- 深入浅出Disruptor
- 深入了解 JSONP
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Python
Paul Barry / O'Reilly Media / 2010-11-30 / USD 49.99
Are you keen to add Python to your programming skills? Learn quickly and have some fun at the same time with Head First Python. This book takes you beyond typical how-to manuals with engaging images, ......一起来看看 《Head First Python》 这本书的介绍吧!