在 F# 中定义非零整数类型
Defining a non zero integer type in F#
如何在 F# 中定义一个非零整数,从而在分配零值时引发编译时错误?
我的问题来自 Scott Wlaschin 视频 https://www.youtube.com/watch?v=E8I19uA-wGY&t=960s 16:00
我在 SO 中找到了这个问题的另一个答案,但都是指动态检查(在创建时抛出异常),但这种方法没什么大不了的,可以用任何 OO 而不是 OO 语言来完成。我正在寻找的是:type NonZeroInteger = int except 0
或类似的东西。
在 F# 中,您想要执行的操作并没有真正的编译时契约。处理此问题的 F# 方法是使用具有私有构造函数的类型 NonZeroInteger
和 returns 和 Option<NonZeroInteger>
的函数。这将确保您永远不会 Some(0)
.
这样做基本上是迫使使用您的代码的开发人员考虑到在构造一个 NonZeroInteger
后,如果给定错误的整数值,他可能没有 NonZeroInteger
的可能性。
在您的代码中,您始终可以安全地假设 NonZeroIntegers 实际上是非零的。
open Option
module A =
type NonZeroInteger =
private | NonZeroInteger of int
static member Create (v : int) : Option<NonZeroInteger> =
if v = 0 then
None
else
Some(NonZeroInteger(v))
member this.Get : int =
this |> fun (NonZeroInteger v) -> v
member this.Print =
this |> fun (NonZeroInteger v) -> printfn "%i" v
printfn "%A" (A.NonZeroInteger(0)) // error FS1093: The union cases or fields of the type 'NonZeroInteger' are not accessible from this code location
let wrong = A.NonZeroInteger.Create(0) // None
let right = A.NonZeroInteger.Create(-1) // Some
wrong |> Option.iter (fun x -> x.Print) // Doesn't print anything
right |> Option.iter (fun x -> x.Print) // Prints -1
private
构造函数可防止模块外的任何人在不通过您的 Create 函数的情况下构造 NonZeroInteger
。
这使得代码非常冗长和缓慢,但安全。所以这里肯定需要权衡。
你不能真的直接这样做,例如使用 Scala 的 require
。
这是一种有点 DDD 风格的方法:
type NonZeroInteger = private NonZeroInteger of int
let createNZI i =
if i = 0 then
Error "NonZeroInteger can't be Zero"
else
Ok (NonZeroInteger i)
构造函数是私有的,因此您必须通过 createNZI 函数(如果更自然,您可以切换名称)。比处理 Result
类型。如果更简单,您可以使用 Some/None 代替。最后,如有必要,使用模式匹配或活动模式解包 NonzeroInteger。
其他答案对于 F# 来说非常惯用,但这里有一种方法可以不可能 构造零值(给调用者带来轻微的不便):
type Digit =
| One = 1
| Two = 2
| Three = 3
| Four = 4
| Five = 5
| Six = 6
| Seven = 7
| Eight = 8
| Nine = 9
type NonZero private(ones, tens, hundreds, thousands, ten_thousands) =
static let f (n : Digit) = int n
member val num = f(ones) + 10 * tens + 100 * hundreds + 1000 * thousands + 10000 * ten_thousands
new (ten_thousands, thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, f ten_thousands)
new (thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, 0)
new (hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, 0, 0)
new (tens, ones) = NonZero(ones, f tens, 0, 0, 0)
new (ones) = NonZero(ones, 0, 0, 0, 0)
这里是你构造数字 123 的方法:
let k = new NonZero(Digit.One, Digit.Two, Digit.Three)
并检索值:
let l = k.num //l is 123 : int
因此,不可能将零值传递给类型为 NonZero -> 'a
的函数。
如何在 F# 中定义一个非零整数,从而在分配零值时引发编译时错误?
我的问题来自 Scott Wlaschin 视频 https://www.youtube.com/watch?v=E8I19uA-wGY&t=960s 16:00
我在 SO 中找到了这个问题的另一个答案,但都是指动态检查(在创建时抛出异常),但这种方法没什么大不了的,可以用任何 OO 而不是 OO 语言来完成。我正在寻找的是:type NonZeroInteger = int except 0
或类似的东西。
在 F# 中,您想要执行的操作并没有真正的编译时契约。处理此问题的 F# 方法是使用具有私有构造函数的类型 NonZeroInteger
和 returns 和 Option<NonZeroInteger>
的函数。这将确保您永远不会 Some(0)
.
这样做基本上是迫使使用您的代码的开发人员考虑到在构造一个 NonZeroInteger
后,如果给定错误的整数值,他可能没有 NonZeroInteger
的可能性。
在您的代码中,您始终可以安全地假设 NonZeroIntegers 实际上是非零的。
open Option
module A =
type NonZeroInteger =
private | NonZeroInteger of int
static member Create (v : int) : Option<NonZeroInteger> =
if v = 0 then
None
else
Some(NonZeroInteger(v))
member this.Get : int =
this |> fun (NonZeroInteger v) -> v
member this.Print =
this |> fun (NonZeroInteger v) -> printfn "%i" v
printfn "%A" (A.NonZeroInteger(0)) // error FS1093: The union cases or fields of the type 'NonZeroInteger' are not accessible from this code location
let wrong = A.NonZeroInteger.Create(0) // None
let right = A.NonZeroInteger.Create(-1) // Some
wrong |> Option.iter (fun x -> x.Print) // Doesn't print anything
right |> Option.iter (fun x -> x.Print) // Prints -1
private
构造函数可防止模块外的任何人在不通过您的 Create 函数的情况下构造 NonZeroInteger
。
这使得代码非常冗长和缓慢,但安全。所以这里肯定需要权衡。
你不能真的直接这样做,例如使用 Scala 的 require
。
这是一种有点 DDD 风格的方法:
type NonZeroInteger = private NonZeroInteger of int
let createNZI i =
if i = 0 then
Error "NonZeroInteger can't be Zero"
else
Ok (NonZeroInteger i)
构造函数是私有的,因此您必须通过 createNZI 函数(如果更自然,您可以切换名称)。比处理 Result
类型。如果更简单,您可以使用 Some/None 代替。最后,如有必要,使用模式匹配或活动模式解包 NonzeroInteger。
其他答案对于 F# 来说非常惯用,但这里有一种方法可以不可能 构造零值(给调用者带来轻微的不便):
type Digit =
| One = 1
| Two = 2
| Three = 3
| Four = 4
| Five = 5
| Six = 6
| Seven = 7
| Eight = 8
| Nine = 9
type NonZero private(ones, tens, hundreds, thousands, ten_thousands) =
static let f (n : Digit) = int n
member val num = f(ones) + 10 * tens + 100 * hundreds + 1000 * thousands + 10000 * ten_thousands
new (ten_thousands, thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, f ten_thousands)
new (thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, 0)
new (hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, 0, 0)
new (tens, ones) = NonZero(ones, f tens, 0, 0, 0)
new (ones) = NonZero(ones, 0, 0, 0, 0)
这里是你构造数字 123 的方法:
let k = new NonZero(Digit.One, Digit.Two, Digit.Three)
并检索值:
let l = k.num //l is 123 : int
因此,不可能将零值传递给类型为 NonZero -> 'a
的函数。