Haskell - 解决循环模块依赖

Posted

技术标签:

【中文标题】Haskell - 解决循环模块依赖【英文标题】:Haskell - resolving cyclical module dependency 【发布时间】:2016-08-27 00:03:06 【问题描述】:

假设我写了以下代码:

一个游戏模块

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,
                 

这保持了模块化,但需要我为我的数据类型添加参数。如果数据结构嵌套得更深,我可能不得不在我的数据中添加大量参数,如果我不得不将这种方法用于多种解决方案,我最终可能会得到数量庞大的类型修饰符。

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

【问题讨论】:

【参考方案1】:

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

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

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

【讨论】:

我强烈建议避免使用-# SOURCE #- / .hs-boot 机制,除非真的有必要。 @leftroundabout:是的,我觉得它既繁琐又不舒服,但是除了wiki 中提到的那些(恕我直言)与小型项目不那么相关的论点之外,还有其他反对它的论据吗?

以上是关于Haskell - 解决循环模块依赖的主要内容,如果未能解决你的问题,请参考以下文章

如何在纯 Haskell 中编写简单的实时游戏循环?

Haskell学习-monad

Haskell - 做while循环

Haskell 导入模块

Haskell 中的模块、包和库有啥区别?

如何在 Haskell 中编写游戏循环?