可以为 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
这对我来说看起来不错,但编译器开始将 Some
和 None
的所有实例视为属于此 DU,而不是 Option
DU。
我不想遍历所有代码,将 Some
替换为 Option.Some
并将 None
替换为 Option.None
。
有没有办法告诉编译器不合格的Some
和None
实际上是Option.Some
和Option.None
?
或者我应该给这些 DU 案例起不同的名字,比如 AtLeastOne
和 ExactlyZero
在 F# 中解决名称冲突的一般规则是 "last declaration wins"。因为您的自定义 DU 是在 Option
之后声明的,所以它的构造函数 Some
和 None
胜过 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
如果检查上面代码中g
、x
、f
和y
的类型:
> g
g : Bogus -> int
> f
f : 'a option -> int
> x
Bogus
> y
int option
函数 g
和值 x
被推断为分别具有类型 Bogus -> int
和 Bogus
,因为 Some
和 None
在他们的身体指的是 Bogus.Some
和 Bogus.None
.
函数 f
和值 y
被推断为具有 Option
相关类型,因为它们的主体中的 Some
和 None
指的是Some
函数和我在上面定义的 (|Some|None|)
活动模式。
当然,这是一种相当老套的恢复现状的方法。这将说服编译器,但人类仍然很难阅读您的代码。我建议您改为重命名 DU 的案例。
您可以使用 [<RequireQualifiedAccess>]
属性标记您的 DU。
这意味着无论何时在代码中使用案例名称,您都需要用类型限定案例名称 - 这是您现在在 match
表达式中所做的事情。
这样,不合格的 Some
仍然会被解析为 Option.Some
,尽管事实上您重复使用了该名称。
知道何时要为 DU 案例使用活泼的名称是一种有用的技术 - 如 None
、Yes
、Failure
等 - 本身就是reader(或编译器,就此而言)含糊不清或令人困惑。
我实现了一个可用于 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
这对我来说看起来不错,但编译器开始将 Some
和 None
的所有实例视为属于此 DU,而不是 Option
DU。
我不想遍历所有代码,将 Some
替换为 Option.Some
并将 None
替换为 Option.None
。
有没有办法告诉编译器不合格的Some
和None
实际上是Option.Some
和Option.None
?
或者我应该给这些 DU 案例起不同的名字,比如 AtLeastOne
和 ExactlyZero
在 F# 中解决名称冲突的一般规则是 "last declaration wins"。因为您的自定义 DU 是在 Option
之后声明的,所以它的构造函数 Some
和 None
胜过 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
如果检查上面代码中g
、x
、f
和y
的类型:
> g
g : Bogus -> int
> f
f : 'a option -> int
> x
Bogus
> y
int option
函数 g
和值 x
被推断为分别具有类型 Bogus -> int
和 Bogus
,因为 Some
和 None
在他们的身体指的是 Bogus.Some
和 Bogus.None
.
函数 f
和值 y
被推断为具有 Option
相关类型,因为它们的主体中的 Some
和 None
指的是Some
函数和我在上面定义的 (|Some|None|)
活动模式。
当然,这是一种相当老套的恢复现状的方法。这将说服编译器,但人类仍然很难阅读您的代码。我建议您改为重命名 DU 的案例。
您可以使用 [<RequireQualifiedAccess>]
属性标记您的 DU。
这意味着无论何时在代码中使用案例名称,您都需要用类型限定案例名称 - 这是您现在在 match
表达式中所做的事情。
这样,不合格的 Some
仍然会被解析为 Option.Some
,尽管事实上您重复使用了该名称。
知道何时要为 DU 案例使用活泼的名称是一种有用的技术 - 如 None
、Yes
、Failure
等 - 本身就是reader(或编译器,就此而言)含糊不清或令人困惑。