選項

簡介

選項值會封裝另一個值,如果沒有任何值可封裝,則包含空值。預定義的 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")

請注意,如果 oNoneget o 會擲回例外狀況。若要存取 option 的內容而不會有引發例外狀況的風險,可以使用類型為 'a option -> 'a -> 'a 的函式 value

let value opt ~default = match opt with
  | Some v -> v
  | None -> default

但是,它需要一個預設值作為額外參數。

在標準函式庫中,這些函式為 Option.getOption.value

摺疊選項

類型為 none:'a -> some:('b -> 'a) -> 'b option -> 'a 的函式 fold 可以被視為 mapvalue 的組合

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 optionbind 函式的運作方式有點像 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>

觀察到除了函式參數的對應域之外,類型相同。

結論

順帶一提,任何可以實作具有類似行為的 mapjoin 函式的型別都可以稱為單子,而 option 通常用於介紹單子。但別驚慌!你不需要知道單子是什麼即可使用 option 型別。

仍然需要協助嗎?

協助改進我們的文件

所有 OCaml 文件都是開放原始碼。看到有錯誤或不清楚的地方嗎?提交提取請求。

OCaml

創新。社群。安全性。