好吧,我觉得这应该可以用另一种方式解决。然而,我提出了以下解决方案,其灵感来自
handler pattern
.
我将hasCheck类型类更改为记录,如下所示:
data Handle = MakeHandle
{ isCollision :: Coord -> Bool
}
然后将所有代码重构为使用handle而不是hasCheck。
collisionKnightRule :: Handle -> Coord -> (Set Coord)
collisionKnightRule handle =
Set.filter (isCollision handle) . knightMoveSet
-- | Set of all moves, legal or not
knightMoveSet :: Coord -> Set Coord
knightMoveSet (x,y) =
Set.fromList
[ (x+2,y-1),(x+2,y+1),(x-2,y-1),(x-2,y+1)
, (x+1,y-2),(x+1,y+2),(x-1,y-2),(x-1,y+2)
]
-- | Set of illegal moves
knightRuleSet :: Handle -> Coord -> (Set Coord)
knightRuleSet =
collisionKnightRule
knightMoves :: Handle -> Coord -> (Set Coord)
knightMoves handle pos =
let
moveSet =
knightMoveSet pos
invalidMoves =
knightRuleSet handle pos
in
Set.difference moveSet invalidMoves
这一点的缺点是,我担心对于有状态的代码,在传递一个过时的句柄(即具有多个真理源)时很容易引入错误。一个优势是,这可能更容易让哈斯克尔的新手理解。现在,我们可以使用QuickCheck的函数typeclass模拟函数,并将其作为参数传递,以生成mockHandler:
knightSetProperty ::
Fun (Int,Int) Bool
-> (Int,Int)
-> Gen Bool
knightSetProperty (Fun _ isCollision) position =
let
handler =
Piece.MakeHandle isCollision
moveSet =
Piece.knightMoves handler position
in
return $ moveSet `Set.isProperSubsetOf` (Piece.knightMoveSet position)
现在,用一个反例来说,这完全失败了:
*** Failed! Falsifiable (after 53 tests and 74 shrinks):
{_->False}
(0,0)