2016-05-02 2 views
5

Скажем, я пишу следующий код:Haskell - разрешение циклического модуля зависимость

игровой модуль

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

модуль

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

игрок и модуль карты

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

Затем я пишу какую-нибудь треску e для реализации логики, когда игроки по очереди рисуют и играют в карты из своей руки, чтобы добавить бонусы к их счету, пока игра не закончится.

Однако после завершения этого кода я понял, что игровой модуль, который я написал, скучен!

Я хочу реорганизовать карточную игру, поэтому, когда вы играете на карточке, а не просто добавляете счет, вместо этого карта произвольно преобразует игру.

Итак, я изменяю Card модуль к следующему

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

, который, конечно, делает импорт модулей образуют цикл.

Как решить эту проблему?

Trivial Решение:

Переместить все файлы в одном модуле. Это хорошо решает проблему, но снижает модульность; Я не могу повторно использовать один и тот же модуль карты для другой игры.

Модуль решение поддержания:

Добавить параметр типа в Card:

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

Добавить другой параметр в Player:

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

С одной окончательной модификации Game:

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

Это сохраняет модульность, но требует от меня добавления параметров к моим типам данных. Если бы структуры данных были более глубоко вложенными, мне бы пришлось добавить к моим данным множество параметров, и если бы мне пришлось использовать этот метод для нескольких решений, я мог бы получить громоздкое количество модификаторов типов.

Итак, есть ли другие полезные решения для решения этого рефактора или это только два варианта?

ответ

6

Ваше решение (добавление параметров типа) не является плохим.Ваши виды становятся все более общими (вы можете использовать Card OtherGame, если вам это нужно), но если вы не любите дополнительные параметры, которые вы можете либо:

  • написать модуль CardGame, который содержит (всего) ваши взаимно рекурсивные типы данных, и импортировать этот модуль в других, или
  • в ghc, используйте {-# SOURCE #-} прагмами для break the circular dependency

Это последнее решение требует написание Card.hs-boot файла с подмножеством объявлений типа в Card.hs.

+3

Я предпочел бы настоятельно рекомендовать избегать механизма '{- # SOURCE # -}'/.hs-boot, если это действительно необходимо. – leftaroundabout

+1

@leftroundabout: Да, я нахожу это неудобным и неудобным, но есть ли какие-либо аргументы против него, чем те, которые упомянуты в [wiki] (https://wiki.haskell.org/Mutually_recursive_modules), которые (imho) не столь актуальным для небольших проектов? –