如何进行重构,使得得分高于特定数量的分数是无法代表的?

时间:2016-04-12 22:47:10

标签: f# refinement-type

如何进行重构,使得得分高于特定数量的分数是无法代表的?

例如,如何使用以下代码并使编译器拒绝任何超过总分为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

2 个答案:

答案 0 :(得分:5)

您希望使用的功能(并且您不是唯一的功能!)称为依赖类型Wikipediaquick 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这样的定理证明器,以确定重复添加点会使你超出有效范围。