Haskell - 解决循环模块依赖

Sol*_*ren 8 haskell cyclic-dependency

假设我写下面的代码:

一个游戏模块

module Game where 
import Player
import Card 
data Game = Game {p1 :: Player,
                  p2 :: Player,
                  isP1sTurn :: Bool
                  turnsLeft :: Int
                 }
Run Code Online (Sandbox Code Playgroud)

一个播放器模块

module Player where
import Card
data Player = Player {score :: Int,
                      hand :: [Card],
                      deck :: [Card]
                     }
Run Code Online (Sandbox Code Playgroud)

和卡模块

module Card where
data Card = Card {name :: String, scoreValue :: Int}
Run Code Online (Sandbox Code Playgroud)

然后我写了一些代码来实现逻辑,玩家轮流从他们的手中抽取牌和牌来为他们的分数增加奖金,直到游戏结束.

但是,我意识到完成这段代码后,我写的游戏模块很无聊!

我想重构纸牌游戏,所以当你玩牌时,而不是仅仅添加一个分数,而是卡片任意改变游戏.

所以,我将Card模块更改为以下内容

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

这当然使模块导入形成一个循环.

我该如何解决这个问题?

琐碎的解决方案:

将所有文件移动到同一模块.这很好地解决了这个问题,但减少了模块化; 我不能在以后为另一个游戏重复使用相同的卡模块.

模块维护解决方案

将类型参数添加到Card:

module Card where
data Card a = {name :: String, onPlayFunc :: (a -> a), scoreValue :: Int}
Run Code Online (Sandbox Code Playgroud)

添加另一个参数Player:

module Player where
data Player a {score :: Int, hand :: [card a], deck :: [card a]}
Run Code Online (Sandbox Code Playgroud)

最后修改为Game:

module Game where
data Game = Game {p1 :: Player Game,
                  p2 :: Player Game,
                 }
Run Code Online (Sandbox Code Playgroud)

这保持了模块化,但要求我为我的数据类型添加参数.如果数据结构有任何更深层次的嵌套,我可能需要在我的数据中添加很多参数,如果我不得不将这个方法用于多个解决方案,我最终会得到一些笨重的类型修饰符.

那么,有没有其他有用的解决方案来解决这个重构,或者这些只是两个选项?

Han*_*Lub 8

您的解决方案(添加类型参数)并不错.您的类型变得更加通用(Card OtherGame如果您需要它可以使用),但如果您不喜欢额外的参数,您可以:

  • 编写一个CardGame包含(仅)您的相互递归数据类型的模块,并将该模块导入其他模块,或者
  • ghc,使用{-# SOURCE #-}编译指示来打破循环依赖

最后一个解决方案需要编写一个Card.hs-boot带有类型声明子集的文件Card.hs.

  • 我宁愿建议避免使用`{ - #SOURCE# - }`/ .hs-boot机制,除非它确实是必要的. (3认同)