Chains of dependent types and their instances when they have different parameter lists in Scala

I am experimenting with writing more statically secure code by implementing a simple card game. There are several unique cards in this game, and each card has a card-specific effect, which may require additional parameters (for example, a target for the effect). The player holds two cards and, in turn, chooses one of them, as a result of which the card effect occurs.

Note. Most of the details in this post can be tried in REPL. I have a less static version of the safe type, but I want to make sure that what I want is possible before diving into it completely.

Here are some relevant definitions:

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
}

: , (, ), (, ). 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(...)
    }
  }
}

factory , 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

,

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

implicits, , 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 - . , , - .

0

Source: https://habr.com/ru/post/1537805/


All Articles