4

簡単なカードゲームを実装することで、より静的にタイプセーフなコードを書く実験をしています。このゲームにはいくつかのユニークなカードがあり、各カードにはカード固有の効果があり、追加のパラメーター (効果のターゲットなど) が必要になる場合があります。プレイヤーは 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)
}

しかし、うさぎの穴に行き過ぎたような気がします。私が達成したいことは可能ですか?それは必要ですか?静的な型の安全性を確保するために型付けを深く掘り下げる必要がありますか、それとも本当に初歩的なことを見逃していますか?

4

1 に答える 1

0

最初の質問では、タプルを関係を表す型に置き換えます

trait CardAndParams {
    type C <: Card
    val card: C
    val params: C#Params
}

def choose[R <: CardAndParams](hand: Hand, gameState: GameState)(
    implicit helper: Helper {type Out = R}): R

Helper実際の戦略の実装を推進し、正しい R が推論されるようにするには、私の例のような暗黙を使用する必要があります。これは、型レベルの計算を行うより一般的な方法でもあります。

sealed trait RandomStrategyHelper[C <: Card] {
    def params(): C#Params
}
object RandomStrategyHelper {
    implicit def forCardA = new RandomStrategyHelper[CardA] {
        def params() = 1 :: HNil
    }
    implicit def forCardB = new RandomStrategyHelper[CardB] {
        def params() = 1 :: 2 :: HNil
    }
}

def randomParams[C <: Card](card: C)(implicit rsh: RandomStrategyHelper[C]) =
    rsh.params()

しかし、ランダムに生成されたカードから強く型付けされたカードに移行する方法が必要だと思います。そのためには、型レベルでランダムなカードを表現するのが難しいため、パターン マッチが適切に思えます。

一般に、この種の型レベルのプログラミングは可能ですが、Scala では困難です。言語は実際にはそのように設計されていません。これを可能な限り推し進めたい場合は、Idris のようなものを使用したほうがよいかもしれません。

于 2014-09-29T23:15:10.547 に答える