如何进行重构,使得得分高于特定数量的分数是无法代表的?
例如,如何使用以下代码并使编译器拒绝任何超过总分为11分的镜头?
let results = (player1, player2) |> makeFieldBasket TwoPointer
|> makeFoulShots ThreeFoulShots
|> makeFieldBasket TwoPointer
|> makeFoulShots TwoFoulShots
|> makeFieldBasket TwoPointer
以上代码的输出如下:
val results : FoulShooter * FieldShooter =
(FoulShooter {Score = 11;}, FieldShooter {Score = 0;})
现在我想构建我的代码,以便无法编译额外的镜头。
例如,我希望编译器拒绝超过11分的额外犯规:
let results = (player1, player2) |> makeFieldBasket TwoPointer
|> makeFoulShots ThreeFoulShots
|> makeFieldBasket TwoPointer
|> makeFoulShots TwoFoulShots
|> makeFieldBasket TwoPointer
|> makeFoulShots FoulShot
目前,上述代码是合法的。
整个代码如下:
(*Types*)
type Player = { Score:int }
type FieldShot = TwoPointer| ThreePointer
type FoulShots = FoulShot | TwoFoulShots | ThreeFoulShots
type FoulShooter = FoulShooter of Player
type FieldShooter = FieldShooter of Player
(*Functions*)
let shoot lastShot player =
(player.Score + lastShot)
let fieldShot (fieldShooter, shot) =
let player = match fieldShooter with
| FieldShooter player -> player
match player.Score with
| score when score >= 11 -> score
| _ -> match (fieldShooter, shot) with
| FieldShooter player, shot -> match shot with
| TwoPointer -> player |> shoot 2
| ThreePointer -> player |> shoot 3
let foulShot (foulShooter, shot) =
let player = match foulShooter with
| FoulShooter player -> player
match player.Score with
| score when score >= 11 -> score
| _ -> match (foulShooter, shot) with
| FoulShooter player, shot -> match shot with
| FoulShot -> player |> shoot 1
| TwoFoulShots -> player |> shoot 2
| ThreeFoulShots -> player |> shoot 3
let makeFoulShots foulShots (shooter, defender) =
FieldShooter { Score= foulShot (shooter, foulShots) }, defender
let makeFieldBasket fieldBasket (shooter, defender) =
FoulShooter { Score= fieldShot (shooter, fieldBasket) }, defender
let turnover (shooter, defender) = (defender, shooter)
(*Client*)
let player1, player2 = FieldShooter { Score=0 } ,
FieldShooter { Score=0 }
let results = (player1, player2) |> makeFieldBasket TwoPointer
|> makeFoulShots ThreeFoulShots
|> makeFieldBasket TwoPointer
|> makeFoulShots TwoFoulShots
|> makeFieldBasket TwoPointer
|> makeFoulShots FoulShot
答案 0 :(得分:5)
您希望使用的功能(并且您不是唯一的功能!)称为依赖类型(Wikipedia和quick introduction)。更确切地说,您的特定示例将被称为细化类型,因为类型Score
对值n
的依赖性由谓词,在这种情况下n <= 11
。
支持依赖类型并非易事。它要求编译器运行完整的定理证明程序,以便正确检查代码中所有可能的执行路径,并确保不,具有'a -> Integer<11>
签名的此函数永远不会返回输出大于11。
依赖类型目前不在主流编程语言(如F#,Haskell,Erlang或Clojure)中实现。但是,它们是用一些学术和/或研究语言实现的,通常是在数学语境中;上面的维基百科文章可能有完整的清单。
如果您需要对依赖类型进行认真的工作, Coq 是其中最成熟且最成熟的,而 Agda 可能是下一个并且更加现代化。
否则,如果您只是处理个人项目,可能需要查看 F* ,这是一种基于活动开发的依赖类型语言在F#上编译,并且应该是最容易接受的。
现在,假设我们只会被&#34;只有&#34;对于当前十年左右的F#,您问题的传统解决方案是将可能无效的值存储在受歧视的联合中。
使用自定义+
运算符的简单DU会阻止您意外添加无效分数:
type Score = InvalidScore | ValidScore of int<pts>
let (+) s1 s2 = match (s1, s2) with
| ValidScore a, ValidScore b when (a + b) <= 11<pts> -> ValidScore (a + b)
| _ -> InvalidScore
如果您还想首先阻止创建无效分数,那么我们需要一个基于访问者修饰符的稍微复杂的实现。
也就是说,我们可以将整个事物放入一个模块中,将DU private
的两个子类放到该模块中,并且只暴露安全的方法/属性,如下所示:
[<AutoOpen>]
module Score =
type Score = private InvalidScore | ValidScore of int<pts> with
static member Create n =
if n > 11<pts> then InvalidScore else ValidScore n
member this.GetPoints =
match this with
| InvalidScore -> None
| ValidScore x -> Some x
let (+) s1 s2 =
match (s1, s2) with
| ValidScore a, ValidScore b when (a + b) <= 11<pts> -> ValidScore (a + b)
| _ -> InvalidScore
let x = ValidScore 12<pts> // won't compile
let y = Score.Create 12<pts> // compiles, but if you call y.GetPoints you get None
答案 1 :(得分:0)
据我所知,在编译时没有办法实现这一点。自定义数字类型可以帮助您将操作保持在有效范围内,并表示(或失败)溢出 - 但它在编译时无效。 (Ab)使用测量单位会有同样的问题。我认为你必须在你的代码上运行像Z3这样的定理证明器,以确定重复添加点会使你超出有效范围。