簡単なカードゲームを実装することで、より静的にタイプセーフなコードを書く実験をしています。このゲームにはいくつかのユニークなカードがあり、各カードにはカード固有の効果があり、追加のパラメーター (効果のターゲットなど) が必要になる場合があります。プレイヤーは 2 枚のカードを持っており、自分のターンにそのうちの 1 枚をプレイすることを選択すると、そのカードの効果が発生します。
注: この投稿の詳細のほとんどは、REPL で試したものです。静的ではない型安全な実装を書いていますが、完全に飛び込む前に、必要なものが実現可能であることを確認したいと思います。
関連する定義を次に示します。
trait CardEffectParams
case class OneTarget(player: Player) extends CardEffectParams
case class TwoTargets(player1: Player, player2: Player) extends CardEffectParams
// ...
trait Card {
// the parameters to use are specific to the card
type Params <: CardEffectParams
}
trait Hand {
case class CardInHand(card: Card) { /* with ctor not accessible from outside */ }
// a player can hold two cards
val card1: CardInHand
val card2: CardInHand
}
どのカードをプレイするかの選択を何らかの戦略に委任して、さまざまな戦略を比較する方法を確認したいと考えています。これは私が立ち往生している場所です: 返すことができるカードをHand
、パラメーターで渡されたオブジェクトのカードに制限したいと思います。これは、次のように入力することで実行できますhand.CardInHand
。
trait Strategy {
def choose(hand: Hand, gameState: GameState): hand.CardsInHand
}
ただし、追加のパラメーターも渡したいと考えています。たとえば、あるカードでは 1 人のプレイヤーのみを対象にすることができます (例: ターンをスキップする) が、別のカードでは 2 人を対象にすることができます (例: カードを入れ替える)。これらは によってモデル化されていCardEffectParams
ます。hand.CardsInHand
だから私は両方を返したいし、私が返すインスタンスはcardInHand.card.Params
どこにあるのか、次のようなものです:cardInHand
/* NOT valid scala */
trait Strategy {
def choose(hand: Hand, gameState: GameState): (c: hand.CardsInHand, c.card.Params)
}
最初の質問は、これができるかどうかです。この関係をどのように表現しますか?
また、サブクラスをインスタンス化する方法にもこだわっていますCardEffectParams
。サブクラスごとに異なるパラメーター リストがある可能性があるためです。最初に考えたのはパターン マッチを行うことですが、マッチ ブロックの型がすべての可能な結果の共通の祖先であるため、これは失敗します。
case object CardA extends Card {
type Params = OneTarget
}
case object CardB extends Card {
type Params = TwoTargets
}
object RandomStrategy extends Strategy {
def choose(hand: Hand, gameState: GameState) = {
val card: Card = /* randomly pick card1 or card2 */
/* the type of the match block is CardEffectParams, not card.Params */
val param: card.Params = card match {
case CardA => OneTarget(...)
case CardB => TwoTargets(...)
}
}
}
私の現在のアイデアは、正しい型を生成する引数の hlist を取る各カード オブジェクト内にファクトリ メソッドを定義することです。
trait Card {
type Params <: CardEffectParams
type HListTypeOfParams = /* insert shapeless magic */
def create[L <: HListTypeOfParams](l: L): Params
}
そこから次のことができますか?
// no idea if this works or not
val card: Card = ...
val params: card.Params = card match {
case c: CardA => c.create(1 :: HNil)
case c: CardB => c.create(1 :: 2 :: HNil)
}
しかし、うさぎの穴に行き過ぎたような気がします。私が達成したいことは可能ですか?それは必要ですか?静的な型の安全性を確保するために型付けを深く掘り下げる必要がありますか、それとも本当に初歩的なことを見逃していますか?