How can I return a parameter as a union case value?

删除回忆录丶 提交于 2019-12-11 04:16:25

问题


How can I return a parameter as a union case value?

I have the following function:

let jumpBlack ((blackChecker:BlackChecker),(blackCheckers:BlackChecker list))  (redPiece:RedPiece) =

    let yIncrementValue = -1
    let minY = 0

    match redPiece with
    | RedPiece.RedChecker rc -> 
       let position = rc.Position |> jump blackChecker.Position yIncrementValue
       match position with
       | pos when pos = rc.Position -> RedPiece.RedChecker { rc with Position= position }, blackCheckers
       | pos when pos.Y = minY      -> RedPiece.RedKing    { RedKing.Position=position },  blackCheckers |> remove blackChecker
       | _ ->                          RedPiece.RedChecker { rc with Position= position }, blackCheckers |> remove blackChecker

    | RedPiece.RedKing rk -> 
       let position = rk.Position |> jump blackChecker.Position yIncrementValue
       match position with
       | pos when pos = rk.Position -> RedPiece.RedKing { rk with Position= position }, blackCheckers
       | pos when pos.Y = minY      -> RedPiece.RedKing { Position=position }, blackCheckers |> remove blackChecker
       | _                          -> RedPiece.RedKing { rk with Position= position }, blackCheckers |> remove blackChecker

Specifically, I want to refactor this part of the above function into one function:

match redPiece with
| RedPiece.RedChecker rc -> 
   let position = rc.Position |> jump blackChecker.Position yIncrementValue
   match position with
   | pos when pos = rc.Position -> RedPiece.RedChecker { rc with Position= position }, blackCheckers
   | pos when pos.Y = minY      -> RedPiece.RedKing    { RedKing.Position=position },  blackCheckers |> remove blackChecker
   | _ ->                          RedPiece.RedChecker { rc with Position= position }, blackCheckers |> remove blackChecker

| RedPiece.RedKing rk -> 
   let position = rk.Position |> jump blackChecker.Position yIncrementValue
   match position with
   | pos when pos = rk.Position -> RedPiece.RedKing { rk with Position= position }, blackCheckers
   | pos when pos.Y = minY      -> RedPiece.RedKing { Position=position }, blackCheckers |> remove blackChecker
   | _                          -> RedPiece.RedKing { rk with Position= position }, blackCheckers |> remove blackChecker

How do I refactor the duplicated code above?

I'm stuck on how to remove the duplication and still return the two different union types (i.e. red checker and red king)

Domain:

(* Types *)
type North = NorthEast | NorthWest
type South = SouthEast | SouthWest

type Direction = 
    | NorthEast 
    | NorthWest
    | SouthEast 
    | SouthWest

type Position =     { X:int; Y:int }

type BlackChecker = { Position:Position }
type RedChecker =   { Position:Position }
type BlackKing =    { Position:Position }
type RedKing =      { Position:Position }

type Checker =
    | BlackChecker of BlackChecker
    | RedChecker   of RedChecker
    | BlackKing    of BlackKing
    | RedKing      of RedKing

type King = 
    | BlackKing of BlackKing
    | RedKing of RedKing

type RedPiece = 
    | RedChecker of RedChecker 
    | RedKing of RedKing

(* Functions *)
let rec remove item list = list |> List.filter (fun x -> x <> item)

let setRowPosition y1 y2 y3 index =
    match index with 
    | x when x < 4 -> { X=x; Y=y1 }
    | x when x < 8 -> { X=x-4; Y=y2 }
    | _            -> { X=index-8; Y=y3 }

let initializeBlack () =
    let setPosition index =
        index |> setRowPosition 7 6 5

    let blackCheckers = List.init 12 setPosition |> List.map (fun pos -> { BlackChecker.Position= { X=pos.X; Y=pos.Y } })
    blackCheckers

let initializeRed () =
    let setPosition index =
        index |> setRowPosition 0 1 2

    let redCheckers =   List.init 12 setPosition |> List.map (fun pos -> { RedChecker.Position= { X=pos.X; Y=pos.Y } })
    redCheckers

let set (x, y) positions (position:Position) =
    match not (positions |> List.exists (fun pos -> pos = { X=x; Y=y })) with
    | true -> { X=x; Y=y }
    | false -> position

let moveBlack direction positions (checker:BlackChecker) =
    let position = checker.Position

    match direction with
    | North.NorthEast -> { BlackChecker.Position= (positions, position) ||> set ((position.X + 1), (position.Y + 1 )) } 
    | North.NorthWest -> { BlackChecker.Position= (positions, position) ||> set ((position.X - 1), (position.Y + 1 )) }

let moveRed direction positions (checker:RedChecker) =
    let position = checker.Position

    match direction with
    | South.SouthEast -> { RedChecker.Position= (positions, position) ||> set ((position.X + 1), (position.Y - 1 )) }
    | South.SouthWest -> { RedChecker.Position= (positions, position) ||> set ((position.X - 1), (position.Y - 1 )) }

let moveKing direction positions (king:King) =

    let position = match king with
                   | King.BlackKing bk -> bk.Position
                   | King.RedKing   rk -> rk.Position

    let result = match direction with
                 | NorthEast -> (positions, position) ||> set ((position.X + 1), (position.Y + 1 ))
                 | NorthWest -> (positions, position) ||> set ((position.X - 1), (position.Y + 1 ))
                 | SouthEast -> (positions, position) ||> set ((position.X + 1), (position.Y - 1 ))
                 | SouthWest -> (positions, position) ||> set ((position.X - 1), (position.Y - 1 ))

    match king with
    | King.BlackKing _ -> King.BlackKing { BlackKing.Position= result }
    | King.RedKing   _ -> King.RedKing   { RedKing.Position=   result }

let jump target yDirection source =
    let updateX value = { X=target.X + value
                          Y=target.Y + yDirection }
    match source with
    | position when position.Y + yDirection = target.Y &&
                    position.X + 1 = target.X -> updateX 1

    | position when position.Y + yDirection = target.Y &&
                    position.X - 1 = target.X -> updateX -1
    | _ -> source

let jumpRed ((redChecker:RedChecker), (redCheckers:RedChecker list)) (blackChecker:BlackChecker) =

    let yIncrementValue = 1
    let maxY = 7
    let position = blackChecker.Position |> jump redChecker.Position yIncrementValue

    match position with
    | pos when pos = blackChecker.Position -> BlackChecker { blackChecker with Position= position }, redCheckers
    | pos when pos.Y = maxY                -> Checker.BlackKing { BlackKing.Position=position }, redCheckers |> remove redChecker
    | _ -> BlackChecker { blackChecker with Position= position }, redCheckers |> remove redChecker

let jumpBlack ((blackChecker:BlackChecker),(blackCheckers:BlackChecker list))  (redPiece:RedPiece) =

    let yIncrementValue = -1
    let minY = 0

    match redPiece with
    | RedPiece.RedChecker rc -> 
       let position = rc.Position |> jump blackChecker.Position yIncrementValue
       match position with
       | pos when pos = rc.Position -> RedPiece.RedChecker { rc with Position= position }, blackCheckers
       | pos when pos.Y = minY      -> RedPiece.RedKing    { RedKing.Position=position },  blackCheckers |> remove blackChecker
       | _ ->                          RedPiece.RedChecker { rc with Position= position }, blackCheckers |> remove blackChecker

    | RedPiece.RedKing rk -> 
       let position = rk.Position |> jump blackChecker.Position yIncrementValue
       match position with
       | pos when pos = rk.Position -> RedPiece.RedKing { rk with Position= position }, blackCheckers
       | pos when pos.Y = minY      -> RedPiece.RedKing { Position=position }, blackCheckers |> remove blackChecker
       | _                          -> RedPiece.RedKing { rk with Position= position }, blackCheckers |> remove blackChecker

Tests:

[<Test>
let ``red king jumps checker``() =
    let blackChecker = { BlackChecker.Position= { X=1 ; Y=1 } }
    let target = (blackChecker, [blackChecker])

    RedKing { RedKing.Position= { X=0 ; Y=2 } } |> jumpBlack target
                                                |> fst
                                                |> should equal (RedPiece.RedKing { RedKing.Position= { X=2 ; Y=0 } })

[<Test>]
let ``black checker removed after being jumped``() =
    let target = { BlackChecker.Position= { X=1 ; Y=1 } }, []
    RedChecker { RedChecker.Position= { X=2 ; Y=2 } } |> jumpBlack target
                                                      |> snd
                                                      |> should equal []
[<Test>]
let ``red checker jumps black checker southeast``() =
    let blackChecker = { BlackChecker.Position= { X=3 ; Y=2 } }
    let target = blackChecker, [blackChecker]

    RedChecker { RedChecker.Position= { X=2 ; Y=3 } } |> jumpBlack target
                                                      |> fst
                                                      |> should equal (RedChecker { RedChecker.Position= { X=4 ; Y=1 } })

回答1:


In the code that you want to refactor, there seemed to be only two places where the two parts of the code do something different - one is the pattern matching (where one looks for RedChecker and another for RedKing) and the other part is the body of the first and the third line of the pattern matching (where one returns RedChecker and another RedKing).

The code also works over different types, but those are all the same type:

type BlackChecker = { Position:Position }
type RedChecker =   { Position:Position }
type BlackKing =    { Position:Position }
type RedKing =      { Position:Position }

Extracting the common part will be a lot easier if you just use the same type for all of these:

type Piece = { Position:Position }
type BlackChecker = Piece
type RedChecker = Piece
type BlackKing = Piece
type RedKing = Piece

So, you need to parameterize the code by two things - both of them can be represented as functions of the following types:

detector       : Checker -> Piece option
wrapper        : Piece -> Checker

The key trick here is that these two functions behave like the discriminated union case - the first one behaves as pattern matching using DU case and the second one behaves like a constructor of the DU case.

Now you can extract the common functionality into something like:

match detector redPiece with
| Some rk -> 
    let position = rk.Position |> jump blackChecker.Position yIncrementValue
    match position with
    | pos when pos = rk.Position -> wrapper { rk with Position= position }, blackCheckers
    | pos when pos.Y = minY      -> RedPiece.RedKing { Position=position }, blackCheckers |> remove blackChecker
    | _                          -> wrapper { rk with Position= position }, blackCheckers |> remove blackChecker

| None -> // Handle the case when it is not the piece we are interested in

I have not tested everything, but hopefully this at least shows the line of thought that you can follow to extract the common functionality from the two parts. That said, I would not be all too worried about refactoring the code - if you repeat similar thing just twice, I feel it is often easier just to keep two copies...




回答2:


Alright your model is really complicated... I made the following simplification:

type Position = { X:int; Y:int }

type Color = 
    | Red
    | Black

type PieceType = 
    | King
    | Checker

type Piece = Color*PieceType*Position

Then translating your jumpBlack function I get:

let jumpBlack ((blackChecker:Piece),(blackCheckers:Piece list))  (redPiece:Piece) =

    let yIncrementValue = -1
    let minY = 0

    match redPiece, blackChecker with
    | (Red, Checker, position), (_, _, blackCheckerPosition) -> 
       let newposition = position |> jump blackCheckerPosition yIncrementValue
       match newposition with
       | pos when pos = position ->    (Red, Checker, pos), blackCheckers
       | pos when pos.Y = minY      -> (Red, King, pos) ,  blackCheckers |> remove blackChecker
       | pos ->                          (Red, Checker, pos), blackCheckers |> remove blackChecker
    | (Red, King, position), (_, _, blackCheckerPosition) -> 
       let newposition = position |> jump blackCheckerPosition yIncrementValue
       match newposition with
       | pos when pos = position -> (Red, King, pos), blackCheckers
       | pos when pos.Y = minY      -> (Red, King, pos), blackCheckers |> remove blackChecker
       | pos                          -> (Red, King, pos), blackCheckers |> remove blackChecker
    | _ -> failwith "Invalid" //Deal with Black pieces here! 

But now, it's really easy to refactor the code since we see that if pos isn't equal to the minY value, it stays the same PieceType, but if it reaches minY it always becomes a King.

let jumpBlackNew ((blackChecker:Piece),(blackCheckers:Piece list))  (redPiece:Piece) =

    let yIncrementValue = -1
    let minY = 0

    match redPiece, blackChecker with
    | (Red, pieceType, position), (_, _, blackCheckerPosition) -> 
       let newposition = position |> jump blackCheckerPosition yIncrementValue
       match newposition with
       | pos when pos = position ->    (Red, pieceType, pos), blackCheckers
       | pos when pos.Y = minY      -> (Red, King, pos) ,  blackCheckers |> remove blackChecker
       | pos ->                        (Red, pieceType, pos), blackCheckers |> remove blackChecker
    | _ -> failwith "Invalid" //Deal with Black pieces here!

This also makes it much easier for you to make jump take in both black and red checkers.



来源:https://stackoverflow.com/questions/38657045/how-can-i-return-a-parameter-as-a-union-case-value

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!