Haskell - resolving dependency of a cyclic module

Let's say I write the following code:

game module

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

player module

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

and card module

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

Then I write code to implement the logic, when players take turns drawing and playing cards from their own hand to add bonuses to their account until the game is over.

However, after completing this code, I realized that the written game module is boring!

I want to reorganize the card game, so when you play on the card, and not just add an account, the card will arbitrarily transform the game instead.

So, I am changing the Card module to the next

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

which, of course, makes importing modules a loop form.

How to solve this problem?

Trivial solution:

Move all files to the same module. This solves the problem well, but reduces modularity; I cannot reuse the same map module for another game.

Module Maintenance 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 of the Game :

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

This is modular, but requires me to add parameters to my data types. If the data structures were more deeply nested, I would have to add a lot of parameters to my data, and if I had to use this method for several solutions, I could get a cumbersome number of type modifiers.

So, are there any other useful solutions to solve this refactor, or are these just two options?

+6
source share
1 answer

Your decision (adding type parameters) is not bad. Your types become more general (you can use Card OtherGame if you need it), but if you don't like the additional options, you can:

  • write a CardGame module that contains (simply) your mutually recursive data types and imports this module into others, or
  • in ghc , use {-# SOURCE #-} pragmas to break the circular dependency

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

+7
source

All Articles