Быстрая проверка с ограничением класса типов и создание отчетов о сгенерированных значениях? - PullRequest
2 голосов
/ 25 марта 2019

Я пытаюсь сделать тест на основе свойств для шахматной игры. Я установил следующий класс типов

class Monad m => HasCheck m where                                                   
    isCollision :: Coord -> m Bool                                                  

, который проверяет, содержит ли данная координата столкновение или находится за его пределами.

Теперь у меня есть функция, которая генерирует набор разрешенных действий для рыцаря, как показано ниже

collisionKnightRule :: HasCheck m => Coord -> m (Set Coord)                      
collisionKnightRule =                                                            
    Set.filterM isCollision . 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)                                
        ]                                                                        



knightMoves :: HasCheck m => Coord -> m (Set Coord)                              
knightMoves pos =                                                                
    do  let moveSet =                                                            
                knightMoveSet pos                                                
        invalidMoves <- collisionKnightRule pos                                        
        return $ Set.difference moveSet invalidMoves                             

и экземпляр класса HasCheck для произвольной координаты

instance HasCheck Gen where                                                      
    isCollision _ =                                                              
         Quickcheck.arbitrary                                                    

и так далее, чтобы проверить это, я хочу убедиться, что сгенерированный набор движений является правильным подмножеством всех возможных ходов.

knightSetProperty :: Piece.HasCheck Gen                                          
    => (Int,Int)                                                                 
    -> Gen Bool                                                                  
knightSetProperty position =                                                     
    do  moves <- Piece.knightMoves position                                      
        return $ moves `Set.isProperSubsetOf` (Piece.knightMoveSet position)

-- ... later on

it "Knight ruleset is subset" $                                          
            quickCheck knightSetProperty

Конечно, это терпит неудачу, потому что может быть, что рыцарь не может никуда двигаться, а это значит, что это не правильное подмножество, а тот же набор. Однако сообщаемая ошибка не особенно полезна

*** Failed! Falsifiable (after 14 tests and 3 shrinks):  
(0,0)

Это потому, что quickcheck не сообщает о сгенерированном значении isCollision. Поэтому мне интересно, как я могу заставить quickCheck сообщать о сгенерированном значении isCollision?

1 Ответ

0 голосов
/ 27 марта 2019

Хорошо, я чувствую, что это должно быть решено другим способом. Однако я сделал следующее решение, которое работает на основе шаблона обработчика .

Я изменил класс типов HasCheck на запись следующим образом:

data Handle = MakeHandle                                                                   
    { isCollision   :: Coord -> Bool                                                   
    }      

и затем рефакторинг всего кода для использования дескриптора вместо 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

Недостатком этого является то, что я боюсь, что для кода с состоянием может быть легко ввести ошибку, когда вы передаете дескриптор, который устарел, т.е. имея несколько источников истины. Преимущество состоит в том, что это, вероятно, легче понять новичкам в Haskell. Теперь мы можем макетировать функции, используя класс типов Quickcheck Function, и передавать их в качестве аргумента для создания 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)
...