2016-05-02 17 views
5

Diciamo che scrivere il codice seguente:Haskell - risolvere modulo ciclico dipendenza

un gioco Modulo

module Game where 
import Player 
import Card 
data Game = Game {p1 :: Player, 
        p2 :: Player, 
        isP1sTurn :: Bool 
        turnsLeft :: Int 
       } 

un giocatore

module Player where 
import Card 
data Player = Player {score :: Int, 
         hand :: [Card], 
         deck :: [Card] 
        } 

e un modulo di carta

module Card where 
data Card = Card {name :: String, scoreValue :: Int} 

Poi scrivo del merluzzo e per implementare la logica in cui i giocatori a turno disegnano e giocano a carte dalla propria mano per aggiungere bonus al proprio punteggio fino a quando il gioco finisce i turni.

Tuttavia, mi rendo conto al termine di questo codice che il modulo di gioco che ho scritto è noioso!

Voglio refactoring del gioco di carte in modo che quando si gioca una carta, piuttosto che solo aggiungendo un punteggio, invece la carta trasforma arbitrariamente il gioco.

Quindi, posso cambiare il modulo Card al seguente

module Card where 
import Game 
data Card = Card {name :: String, 
        onPlayFunction :: (Game -> Game)    
        scoreValue :: Int} 

che ovviamente rende le importazioni dei moduli formano un ciclo.

Come posso risolvere questo problema?

Trivial Soluzione:

spostare tutti i file allo stesso modulo. Questo risolve bene il problema, ma riduce la modularità; Non posso più riutilizzare lo stesso modulo per un'altra partita.

Modulo soluzione mantenimento:

Aggiungere un parametro di tipo a Card:

module Card where 
data Card a = {name :: String, onPlayFunc :: (a -> a), scoreValue :: Int} 

Aggiungere un altro parametro per Player:

module Player where 
data Player a {score :: Int, hand :: [card a], deck :: [card a]} 

Con una modifica finale Game:

module Game where 
data Game = Game {p1 :: Player Game, 
        p2 :: Player Game, 
       } 

Ciò mantiene la modularità, ma richiede l'aggiunta di parametri ai miei tipi di dati. Se le strutture dei dati fossero più profondamente annidate, potrei aggiungere un sacco di parametri ai miei dati e, se dovessi usare questo metodo per più soluzioni, potrei finire con un numero ingombrante di modificatori di tipi.

Quindi, ci sono altre soluzioni utili per risolvere questo refactoring, o sono queste le uniche due opzioni?

risposta

6

La soluzione (aggiunta di parametri di tipo) non è negativa.I suoi tipi diventano più generale (si potrebbe usare Card OtherGame se ne avete bisogno), ma se non ti piace il parametro aggiuntivo che potrebbe o:

  • scrivere un modulo CardGame che contiene (solo) i tuoi tipi di dati reciprocamente ricorsive, e importare questo modulo gli altri, o
  • in ghc, utilizzare {-# SOURCE #-} pragma per break the circular dependency

Quest'ultima soluzione richiede la scrittura di un file Card.hs-boot con un sottoinsieme delle dichiarazioni di tipo in Card.hs.

+3

Preferisco fortemente raccomandare di evitare il meccanismo '{- # SOURCE # -}'/.hs-boot, a meno che non sia veramente necessario. – leftaroundabout

+1

@leftroundabout: Sì, lo trovo laborioso e scomodo, ma ci sono argomenti contro di esso diversi da quelli menzionati nel [wiki] (https://wiki.haskell.org/Mutually_recursive_modules), che sono (imho) non così rilevante per i piccoli progetti? –