选项

简介

一个 选项 值封装另一个值,或者如果没有任何东西要封装则不包含任何内容。预定义类型 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 处理而不是异常时,选项类型很有用。它是返回错误值的类型安全版本。由于没有封装的数据有任何特殊含义,因此不可能混淆常规值和值的缺失。

异常 vs 选项

函数 Sys.getenv : string -> string 来自 OCaml 标准库,查询环境变量的值;但是,如果变量未定义,则会抛出异常。另一方面,函数 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 模块提供。

在选项上映射

使用模式匹配,可以定义函数,允许处理选项值。以下是类型为 ('a -> 'b) -> 'a option -> 'b optionmap。它允许将函数应用于 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

剥离双重包装的选项

以下是类型为 'a option option -> 'a optionjoin。它从双重包装的选项中剥离一层

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")

注意,如果 oNone,则 get 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 期望一个返回类型为 b 的未包装值的函数参数 f,但 bind 期望一个返回已包装在选项 'b option 中的值的 f

在这里,我们显示 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

创新。社区。安全。