可以为 Discriminated Union 类型设置默认值吗?

Can one set default values for Discriminated Union types?

我实现了一个可用于 select 函数的区分联合类型:

type BooleanCombinator =
    | All
    | Some
    | None
    | AtLeast of int
    | MoreThan of int
    | NotMoreThan of int
    | LessThan of int
    | ExactlyOne
    | ExactlyTwo
    | AllButOne
    | AllButTwo

let boolToInt (b: bool) : int = if b then 1 else 0

let combineBooleans (combinator : BooleanCombinator)
                    (bools      : bool list)
                                : bool =

        let n = List.sumBy boolToInt bools

        match combinator with
        | BooleanCombinator.All -> List.forall id bools
        | BooleanCombinator.Some -> bools |> List.exists id
        | BooleanCombinator.None -> bools |> List.exists id |> not
        | BooleanCombinator.AtLeast i -> n >= i
        | BooleanCombinator.MoreThan i -> n > i
        | BooleanCombinator.NotMoreThan i -> n <= i
        | BooleanCombinator.LessThan i -> n < i
        | BooleanCombinator.ExactlyOne -> n = 1
        | BooleanCombinator.ExactlyTwo -> n = 2
        | BooleanCombinator.AllButOne -> n = bools.Length - 1
        | BooleanCombinator.AllButTwo -> n = bools.Length - 2

这对我来说看起来不错,但编译器开始将 SomeNone 的所有实例视为属于此 DU,而不是 Option DU。

我不想遍历所有代码,将 Some 替换为 Option.Some 并将 None 替换为 Option.None

有没有办法告诉编译器不合格的SomeNone实际上是Option.SomeOption.None

或者我应该给这些 DU 案例起不同的名字,比如 AtLeastOneExactlyZero

在 F# 中解决名称冲突的一般规则是 "last declaration wins"。因为您的自定义 DU 是在 Option 之后声明的,所以它的构造函数 SomeNone 胜过 Option.

的构造函数

但此规则提供了一种解决问题的方法:您只需要 "reassert" 自定义 DU 之后的声明:

type Bogus = Some of int | None

let g = function Some _ -> 42 | None -> 5
let x = Some 42

let inline Some a = Option.Some a
let inline None<'a> = Option.None : 'a option
let (|Some|None|) = function | Option.Some a -> Some a | Option.None -> None

let f = function Some _ -> 42 | None -> 5
let y = Some 42

如果检查上面代码中gxfy的类型:

> g
g : Bogus -> int

> f
f : 'a option -> int

> x
Bogus

> y
int option

函数 g 和值 x 被推断为分别具有类型 Bogus -> intBogus,因为 SomeNone 在他们的身体指的是 Bogus.SomeBogus.None.

函数 f 和值 y 被推断为具有 Option 相关类型,因为它们的主体中的 SomeNone 指的是Some 函数和我在上面定义的 (|Some|None|) 活动模式。

当然,这是一种相当老套的恢复现状的方法。这将说服编译器,但人类仍然很难阅读您的代码。我建议您改为重命名 DU 的案例。

您可以使用 [<RequireQualifiedAccess>] 属性标记您的 DU。

这意味着无论何时在代码中使用案例名称,您都需要用类型限定案例名称 - 这是您现在在 match 表达式中所做的事情。

这样,不合格的 Some 仍然会被解析为 Option.Some,尽管事实上您重复使用了该名称。

知道何时要为 DU 案例使用活泼的名称是一种有用的技术 - 如 NoneYesFailure 等 - 本身就是reader(或编译器,就此而言)含糊不清或令人困惑。