Haskell - 解决循环模块依赖

Haskell - resolving cyclical module dependency

假设我写了下面的代码:

一个游戏模块

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

一个播放器模块

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

和卡片模块

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

然后我编写了一些代码来实现玩家轮流从手中抽牌和打牌以增加分数的逻辑,直到游戏结束为止。

但是写完这段代码才发现自己写的游戏模块很无聊!

我想重构纸牌游戏,所以当你玩纸牌时,不是仅仅增加分数,而是纸牌任意改变游戏。

因此,我将 Card 模块更改为以下

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

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

如何解决这个问题?

简单的解决方案:

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

模块维护方案:

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,
                 }

这保持了模块化,但需要我向我的数据类型添加参数。如果数据结构嵌套得更深,我可能不得不向我的数据添加 - 很多 - 参数,并且如果我必须将此方法用于多个解决方案,我最终可能会得到大量笨拙的类型修饰符。

那么,是否有任何其他有用的解决方案来解决此重构,或者这些是唯一的两个选项吗?

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

  • 编写一个包含(仅)您的相互递归数据类型的模块 CardGame,然后将此模块导入其他模块,或者
  • ghc 中,使用 {-# SOURCE #-} 编译指示 break the circular dependency

最后一个解决方案需要编写一个 Card.hs-boot 文件,其中包含 Card.hs 中类型声明的子集。