选项
简介
一个 选项 值封装另一个值,或者如果没有任何东西要封装则不包含任何内容。预定义类型 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 option
的 map
。它允许将函数应用于 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 option
的 join
。它从双重包装的选项中剥离一层
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
期望一个返回类型为 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>
观察到类型相同,除了函数参数的陪域。
结论
顺便说一句,任何可以实现 map
和 join
函数且行为类似的类型都可以称为单子,并且 option
通常用于引入单子。但不要惊慌!您无需了解什么是单子即可使用 option
类型。