2016-05-02 11 views
5

Sagen wir, ich den folgenden Code schreiben:Haskell - Lösung zyklischen Modulabhängigkeits

ein Spielmodul

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

ein Spieler Modul

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

und ein Kartenmodul

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

Ich schreibe dann etwas Kabeljau e Logik zu implementieren, bei der die Spieler abwechselnd Karten aus ihrer Hand ziehen und spielen, um Boni zu ihren Punktzahlen hinzuzufügen, bis das Spiel aus den Zügen ausgeht.

Allerdings merke ich nach Abschluss dieses Codes, dass das Spielmodul, das ich geschrieben habe, langweilig ist!

Ich möchte das Kartenspiel umgestalten, also wenn Sie eine Karte spielen, anstatt nur eine Kerbe hinzuzufügen, stattdessen verwandelt die Karte willkürlich das Spiel.

So ändere ich den Card Modul an folgende

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

was natürlich die Modulimporte einen Zyklus macht bilden.

Wie behebe ich dieses Problem?

Trivial Lösung:

Verschieben Sie alle Dateien auf dem gleichen Modul. Dies löst das Problem gut, reduziert aber die Modularität; Ich kann das gleiche Kartenmodul später nicht für ein anderes Spiel wiederverwenden.

Modul Aufrechterhaltung Lösung:

hinzufügen Typ Parameter Card:

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

einen anderen Parameter In den Player:

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

Mit einem letzten Änderung Game:

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

Das hält die Modularität, aber ich muss meine Datentypen Parameter hinzufügen. Wenn die Datenstrukturen tiefer verschachtelt wären, könnte ich meinen Daten viele Parameter hinzufügen müssen, und wenn ich diese Methode für mehrere Lösungen verwenden müsste, könnte ich mit einer unhandlichen Anzahl von Typmodifikatoren enden.

Gibt es also andere nützliche Lösungen zur Lösung dieses Refactors, oder sind dies die einzigen zwei Optionen?

Antwort

6

Ihre Lösung (Hinzufügen von Typparametern) ist nicht schlecht.Ihre Typen werden allgemeinere (man könnte Card OtherGame verwenden, wenn Sie es brauchen), aber wenn man die zusätzlichen Parameter nicht mögen Sie können entweder:

  • schreiben ein Modul CardGame, die (nur) enthält Ihr gegenseitig rekursiven Datentypen, und importieren diese Modul in den anderen, oder
  • in ghc verwendet {-# SOURCE #-} Pragmas break the circular dependency

Diese letzte Lösung erfordert das Schreiben einer Card.hs-boot Datei mit einer Teilmenge der Typdeklarationen in Card.hs.

+3

Ich würde eher empfehlen, die '{- # SOURCE # -}'/.hs-Boot-Mechanismus zu vermeiden, es sei denn, es ist wirklich notwendig. – leftaroundabout

+1

@leftroundabout: Ja, ich finde es fummelig und unbehaglich, aber gibt es irgendwelche Argumente dagegen anders als die im [Wiki] genannten (https://wiki.haskell.org/Mutually_recursive_modules), die (imho) nicht sind so relevant für kleine Projekte? –