Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - resolving cyclical module dependency

Let's say I write the following code:

a game module

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

a player module

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

and a card module

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

I then write some code to implement logic where players take turns drawing and playing cards from their hand to add bonuses to their score's until the game runs out of turns.

However, I realize upon completion of this code that the game module I've written is boring!

I want to refactor the card game so when you play a card, rather than just adding a score, instead the card arbitrarily transforms the game.

So, I change the Card module to the following

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

which of course makes the module imports form a cycle.

How do I resolve this problem?

Trivial Solution:

Move all the files to the same module. This solves the problem nicely, but reduces modularity; I can't later reuse the same card module for another game.

Module maintaining solution:

Add a type parameter to Card:

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

Add another parameter to Player:

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

With one final modification to Game:

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

This keeps modularity, but requires me to add parameters to my data types. If the data structures were any more deeply nested I could have to add -a lot- of parameters to my data, and if I had to use this method for multiple solutions, I could end up with an unwieldy number of type modifiers.

So, are there any other useful solutions to resolving this refactor, or are these the only two options?

like image 319
SolventGren Avatar asked May 02 '16 09:05

SolventGren


1 Answers

Your solution (adding type parameters) is not a bad one. Your types become more general (you could use Card OtherGame if you need it), but if you dislike the extra parameters you could either:

  • write a module CardGame that contains (just) your mutually recursive datatypes, and import this module in the other ones, or
  • in ghc, use {-# SOURCE #-} pragmas to break the circular dependency

This last solution requires the writing of a Card.hs-boot file with a subset of the type declarations in Card.hs.

like image 158
Hans Lub Avatar answered Sep 29 '22 00:09

Hans Lub