F#中的选项类型是如何工作的?

10

最近我在阅读Apress出版社的《Expert F#》书籍,主要是在构建一个玩具般的F#库时使用它作为参考资料,但有一件事情我无法理解,那就是“Option”类型。

它是如何工作的,以及它在现实世界中的用途是什么?

6个回答

21
选项类型至少类似于 C# 中的 Nullable 和引用类型。Option 类型的值是 None 或 Some,None 表示没有封装的值,而 Some 则具有特定的 T 值。这就像在 C# 中 Nullable 可以是 null 值或者与 int 关联,以及 String 值可以是空引用或者引用一个 String 对象。
当您使用 option 值时,通常会指定两条路径 - 一条适用于存在相关值的情况,另一条适用于不存在相关值的情况。换句话说,以下代码:
let stringLength (str:Option<string>) =
  match str with
  | Some(v) -> v.Length
  | None -> -1

类似于:

int StringLength(string str)
{
    if (str != null)
    {
        return str.Length;
    }
    else
    {
        return -1;
    }
}

我认为一般的想法是,强制你(几乎)处理“没有关联值/对象”的情况可以使你的代码更加健壮。


字符串长度示例通常不是函数式编程的实现方式。这种方式会强制将不安全的业务逻辑传递下去(检查-1并记住它)。更符合惯用法的方式是告知使用者没有字符串(null或None),并返回Some lengthNone。这将使您的编码更加安全,无NRE,并且希望更少出现错误。为了帮助您做到这一点,以便您不必一遍又一遍地重复匹配,FP定义了Option.map/bind函数。不过,您最后的陈述非常正确 :) - Abel
(顺带一提,F#中的字符串大多是安全的,除非它们来自不受信任的源,这进一步减轻了对空值检查的需求) - Abel

6

5

当函数或方法“可能”或“可选”返回值时使用它。在C#中,您可能会返回null,或者返回Null Object或可能是值类型的Nullable。

返回null(最常见的情况)的缺点是它不是类型安全的:null是所有类型的实例,因此您会在以后遇到各种棘手的空引用情况。

Option类型是一种所谓的带有两个构造函数的区分联合类型:None和Some a。 None明确表示您没有值。基本上,这是通用的Null Object模式。


3

除了其他回答之外,Option类型并没有什么特别之处——它只是另一个带标签的联合类型。你可以在一行代码中自己定义它:

type 'a Option = None | Some of 'a

作为其他人指出的一样,模式匹配的实用性在于它可以安全地解构这个值,而不是检查 null 或使用某些 hack-workaround 来指示一个值是否真的是一个值。

2

当一个值是可选的时候,你可以使用它。其中一种用途是创建一种类似于“空引用”的效果,例如:

 val x : int option ref = None 

然后您可以稍后将x更新为某个v。您可以在match语句中使用它,例如:

 match !x with (* dereference x *)
    None -> (* No value, do something *)
 |  Some v -> (* Value v, do something else *)

我知道这个问题很久以前就有答案了,但一般来说,在 F# 中不鼓励使用引用。当然,这是可能的,但通常是代码异味的标志。最好只使用选项,您可以免费获得针对“无值”和 NRE 的类型安全保护。 - Abel

1

使用选项类型的功能模式:

当您需要更改递归数据结构(例如树或列表)的某些部分时,您将希望尽可能重用现有数据结构。选项类型可以帮助您实现这一点。 这两个函数都将数字5的所有出现替换为7,但第一个函数复制了整个树。第二个函数则没有。

type Tree = Leaf of int
      | Node of Tree * Tree

let rec replace_no_sharing tree =
    match tree with
    | Leaf 5      -> Leaf 7
    | Leaf x      -> Leaf x
    | Node (a, b) -> Node (replace_no_sharing a, replace_no_sharing b)

let replace_with_sharing tree =
    let rec replace_option tree =
        match tree with
        | Leaf 5      -> Leaf 7 |> Some
        | Leaf x      -> None
        | Node (a, b) -> match replace_option a, replace_option b with
                         | None, None     -> None
                         | Some a, Some b -> Node (a, b) |> Some
                         | Some a, None   -> Node (a, b) |> Some
                         | None, Some b   -> Node (a, b) |> Some
    match replace_option tree with
    | None      -> tree
    | Some tree -> tree

在所有情况下可能并不是必要的,但这是一个不错的技巧需要了解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接