2016-05-02 33 views
5

比方說,我寫了下面的代碼:哈斯克爾 - 解決週期性模塊依賴

遊戲模塊

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} 

然後我寫一些鱈魚e實現邏輯,讓玩家輪流從他們的手中抽取和打牌,爲他們的分數添加獎勵,直到遊戲結束。

但是,當我完成這段代碼後,我意識到我寫的遊戲模塊很無聊!

我想重構紙牌遊戲,所以當你玩牌時,而不是僅僅增加一個分數,而是該牌可以任意轉換遊戲。

所以,我的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, 
       } 

這保持了模塊化,但要求我爲我的數據類型添加參數。如果數據結構更深層嵌套,我可能必須添加大量的參數給我的數據,如果我必須將這種方法用於多個解決方案,那麼我最終可能會得到一個難以處理的數量的類型修飾符。

那麼,有沒有其他有用的解決方案來解決這個重構,還是隻有這兩個選項?

回答

6

您的解決方案(添加類型參數)並不錯。你的類型變得更普遍的(如果你需要它,你可以使用Card OtherGame),但是如果你不喜歡,你可以在額外的參數之一:

  • 編寫模塊CardGame含有(只)您的相互遞歸的數據類型,並導入此模塊中其他的人,或在ghc
  • ,使用{-# SOURCE #-}的pragma break the circular dependency

這最後的解決方案需要Card.hs-boot文件與類型聲明中一個子集書寫10。

+3

我寧願強烈建議避免使用'{ - #SOURCE# - }'/ .hs-boot機制,除非真的有必要。 – leftaroundabout

+1

@leftroundabout:是的,我覺得它很煩瑣,但是有沒有比[wiki](https://wiki.haskell.org/Mutually_recursive_modules)中提到的更有爭議,它們是(imho)不是與小項目相關? –