Simplificar algum código Haskell
Pergunta
Então, eu estou trabalhando em uma implementação minimax por damas-como o jogo para me ajudar aprender Haskell melhor. A função que eu estou tendo problemas com recebe uma lista de estados de jogo, e gera a lista de estados de jogo sucessor imediato. Como damas, se um salto está disponível, o jogador deve tomá-lo. Se houver mais de um, o jogador pode escolher.
Para a maior parte, isso funciona muito bem com a lista mônada: loop sobre todos os estados do jogo de entrada, um loop sobre todos os mármores que poderia ser um salto, laço sobre todos os saltos de que o mármore. Esta lista mônada bem achata todas as listas fora em uma simples lista de estados no final.
O truque é que, se há saltos são encontrados para um determinado estado do jogo, eu preciso voltar o estado do jogo atual, em vez de uma lista vazia. O código abaixo é a melhor maneira que eu vim acima com de fazer isso, mas parece realmente feio para mim. Todas as sugestões sobre como limpá-lo?
eHex :: Coord -> Coord -- Returns the coordinates immediately to the east on the board
nwHex :: Coord -> Coord -- Returns the coordinates immediately to the northwest on the board
generateJumpsIter :: [ZertzState] -> [ZertzState]
generateJumpsIter states = do
ws <- states
case children ws of
[] -> return ws
n@_ -> n
where
children ws@(ZertzState s1 s2 b p) = do
(c, color) <- occupiedCoords ws
(start, end) <- [(eHex, wHex), (wHex, eHex), (swHex, neHex),
(neHex, swHex), (nwHex, seHex), (seHex, nwHex)]
if (hexOccupied b $ start c) && (hexOpen b $ end c)
then case p of
1 -> return $ ZertzState (scoreMarble s1 color) s2
(jumpMarble (start c) c (end c) b) p
(-1) -> return $ ZertzState s1 (scoreMarble s2 color)
(jumpMarble (start c) c (end c) b) p
else []
EDIT:. Fornecer assinaturas exemplo de tipo para as funções * Hex
Solução
O truque é que, se há saltos são encontrados para um determinado estado do jogo, eu preciso voltar o estado do jogo atual, em vez de uma lista vazia.
Por quê? Eu escrevi minimax várias vezes, e eu não posso imaginar um uso para tal função a. você não seria melhor fora com uma função do tipo
nextStates :: [ZertzState] -> [Maybe [ZertzState]]
ou
nextStates :: [ZertzState] -> [[ZertzState]]
No entanto, se você realmente quer retorno "ou a lista de próximos estados, ou se essa lista estiver vazia, o estado original", em seguida, o tipo que você quer é
nextStates :: [ZertzState] -> [Either ZertzState [ZertzState]]
que você pode então achatar com bastante facilidade.
Quanto à forma de implementar, eu recomendo a definição de uma função auxiliar do tipo
[ZertzState] -> [(ZertzState, [ZertzState])]
e do que você pode mapear
(\(start, succs) -> if null succs then Left start else Right succs)
sobre o resultado, além de várias outras coisas.
Como Fred Brooks disse (parafraseando), uma vez que você obter os tipos de direito, o código praticamente se escreve.
Outras dicas
Não abuse monads notação para lista, é tão pesado para nada. Além disso, você pode usar compreensão da lista da mesma forma:
do x <- [1..3]
y <- [2..5] <=> [ x + y | x <- [1..3], y <- [2..5] ]
return x + y
Agora, para a 'simplificação'
listOfHex :: [(Coord -> Coord,Coord -> Coord)]
listOfHex = [ (eHex, wHex), (wHex, eHex), (swHex, neHex)
, (neHex, swHex), (nwHex, seHex), (seHex, nwHex)]
generateJumpsIter :: [ZertzState] -> [ZertzState]
generateJumpsIter states =
[if null ws then ws else children ws | ws <- states]
where -- I named it foo because I don t know what it do....
foo True 1 = ZertzState (scoreMarble s1 color) s2
(jumpMarble (start c) c (end c) b) p
foo True (-1) = ZertzState s1 (scoreMarble s2 color)
(jumpMarble (start c) c (end c) b) p
foo False _ = []
foo _ _ = error "Bleh"
children ws@(ZertzState s1 s2 b p) =
[ foo (valid c hex) p | (c, _) <- occupiedCoords ws, hex <- listOfHex ]
where valid c (start, end) =
(hexOccupied b $ start c) && (hexOpen b $ end c)
O deixar entrar a deixar na lista commprehension no topo me incomoda um pouco, mas como eu não tenho todo o código, eu realmente não sei como fazê-lo em uma outra maneira. Se você pode modificar mais em profundidade, eu sugiro que você use mais combinadores (mapa, foldr, foldl' etc) como elas realmente reduzir o tamanho do código na minha experiência.
Note, o código não é testado, e pode não ser compilado.