13

次のコードを書いたとしましょう:

ゲームモジュール

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

に 1 つの最終的な変更を加えると、次のようになりGameます。

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

これによりモジュール性が維持されますが、データ型にパラメーターを追加する必要があります。データ構造がこれ以上深くネストされている場合、データに大量のパラメーターを追加する必要があり、この方法を複数のソリューションに使用する必要がある場合は、扱いにくい数の型修飾子が必要になる可能性があります。

では、このリファクタリングを解決するための他の有用な解決策はありますか、それともこれらの 2 つのオプションしかありませんか?

4

1 に答える 1

10

あなたの解決策(型パラメータを追加する)は悪いものではありません。型はより一般的になります (Card OtherGame必要に応じて使用できます) が、追加のパラメーターが気に入らない場合は、次のいずれかを実行できます。

  • CardGame相互に再帰的なデータ型を (ちょうど) 含むモジュールを作成し、このモジュールを他のモジュールにインポートするか、
  • で、プラグマをghc使用して循環依存を解消します{-# SOURCE #-}

この最後の解決策ではCard.hs-boot、 の型宣言のサブセットを含むファイルを作成する必要がありますCard.hs

于 2016-05-02T09:30:20.340 に答える