選項
簡介
選項值會封裝另一個值,如果沒有任何值可封裝,則包含空值。預定義的 option
型別表示這種值。
# #show option;;
type 'a option = None | Some of 'a
以下是一些選項值
# Some 42;;
- : int option = Some 42
# None;;
- : 'a option = None
這裡我們有
- 42,使用
Some
建構子儲存在option
內部,以及 - 不儲存任何值的
None
值。
當資料的缺乏最好以特殊的 None
值而不是例外狀況來處理時,選項型別很有用。它是返回錯誤值的型別安全版本。由於沒有封裝的資料具有任何特殊意義,因此不可能混淆一般值和缺少值。
例外狀況與選項
OCaml 標準函式庫中的函式 Sys.getenv : string -> string
會查詢環境變數的值;但是,如果未定義該變數,則會擲回例外狀況。另一方面,函式 Sys.getenv_opt : string -> string option
會執行相同的操作,但如果未定義變數,則會返回 None
。以下是如果嘗試存取未定義的環境變數可能會發生的情況
# Sys.getenv "UNDEFINED_ENVIRONMENT_VARIABLE";;
Exception: Not_found.
# Sys.getenv_opt "UNDEFINED_ENVIRONMENT_VARIABLE";;
- : string option = None
請參閱錯誤處理,以獲取有關使用選項、例外狀況和其他方式進行錯誤處理的更長討論。
Option
模組
標準函式庫 本節中的大多數函式以及其他有用的函式,都由 OCaml 標準函式庫在 Stdlib.Option
模組中提供。
映射選項
使用模式匹配,可以定義函式,以允許使用選項值。以下是 map
,其類型為 ('a -> 'b) -> 'a option -> 'b option
。它允許將函式應用於 option
內部封裝的值(如果存在)
let map f = function
| None -> None
| Some v -> Some (f v)
在標準函式庫中,它是 Option.map
。
# Option.map (fun x -> x * x) (Some 3);;
- : int option = Some 9
# Option.map (fun x -> x * x) None;;
- : int option : None
剝離雙重封裝選項
以下是 join
,其類型為 'a option option -> 'a option
。它從雙重封裝的選項中剝離一層
let join = function
| Some Some v -> Some v
| Some None -> None
| None -> None
在標準函式庫中,它是 Option.join
。
# Option.join (Some (Some 42));;
- : int option = Some 42
# Option.join (Some None);;
- : 'a option = None
# Option.join None;;
- : 'a option = None
存取選項的內容
類型為 'a option -> 'a
的函式 get
允許存取 option
內部包含的值。
let get = function
| Some v -> v
| None -> raise (Invalid_argument "option is None")
請注意,如果 o
是 None
,get o
會擲回例外狀況。若要存取 option
的內容而不會有引發例外狀況的風險,可以使用類型為 'a option -> 'a -> 'a
的函式 value
let value opt ~default = match opt with
| Some v -> v
| None -> default
但是,它需要一個預設值作為額外參數。
在標準函式庫中,這些函式為 Option.get
和 Option.value
。
摺疊選項
類型為 none:'a -> some:('b -> 'a) -> 'b option -> 'a
的函式 fold
可以被視為 map
和 value
的組合
let fold ~none ~some o = o |> Option.map some |> Option.value ~default:none
在標準函式庫中,此函式為 Option.fold
。
Option.fold
函式可用於實作後備邏輯,而無需編寫模式匹配。例如,以下是一個函式,將 $PATH
環境變數的內容轉換為字串列表,如果未定義,則轉換為空列表。此版本使用模式匹配
# let path () =
let split_on_colon = String.split_on_char ':' in
let opt = Sys.getenv_opt "PATH" in
match opt with
| Some s -> split_on_colon s
| None -> [];;
val path : unit -> string list = <fun>
以下是使用 Option.fold
的相同函式
# let path () =
let split_on_colon = String.split_on_char ':' in
Sys.getenv_opt "PATH" |> Option.fold ~some:split_on_colon ~none:[];;
val path : unit -> string list = <fun>
綁定選項
類型為 'a option -> ('a -> 'b option) -> 'b option
的 bind
函式的運作方式有點像 map
。但是,map
期望函式參數 f
返回類型為 b
的未封裝值,而 bind
期望 f
返回已封裝在選項 'b option
中的值。
在這裡,我們顯示參數已翻轉的 Option.map
的類型,並顯示 Option.bind
的可能實作。
# Fun.flip Option.map;;
- : 'a option -> ('a -> 'b) -> 'b option = <fun>
# let bind o f = o |> Option.map f |> Option.join;;
val bind : 'a option -> ('a -> 'b option) -> 'b option = <fun>
觀察到除了函式參數的對應域之外,類型相同。
結論
順帶一提,任何可以實作具有類似行為的 map
和 join
函式的型別都可以稱為單子,而 option
通常用於介紹單子。但別驚慌!你不需要知道單子是什麼即可使用 option
型別。