简体   繁体   中英

Scala: How to pattern-match the enclosing object of an inner case class?

I have an inner case class, specifically an event from this question, and want to match it, including the outer object:

class Player {
  var _life = 20
  def life = _life

  def gainLife(life: Int) = execute(GainLife(life))

  case class GainLife(life: Int) extends Event {
    def execute() = _life += life
  }
}

I can easily write an effect (a partial function) that replaces life events for a specific player:

//gain twice as much life
def effect(player: Player): ReplacementEffect = {
  case player.GainLife(x) => player.GainLife(x * 2)
}

however, I can't do the same for other players. The closest I have come is this:

//only you gain life
def effect2(player: Player): ReplacementEffect = {
  case evt: Player#GainLife => player.GainLife(evt.life)
}

But 1) this replaces even your own lifegain with a new lifegain, 2) I can't reference the player that originally gained life in the function and 3) I'm missing out on directly matching life this way.

This could be expressed using a path-independent type like

case Player.GainLife(_player, life) if _player != player => GainLife(player, life)

Ideally, I want something like

case _player.GainLife(life) if _player != player => player.GainLife(life)

Is this possible somehow, or can I work around this? Or do I have to resort to making GainLife nested?

When you define the class inside of another it means that the type is specific to the surrounding class, so playerA.GainLife is not the same type as playerB.GainLife (this is called path dependent types) if you want it to mean the same thing you define it in a scope that is the same regardless of instance: the package or the companion object of your class.

You can read more in this question: What is meant by Scala's path-dependent types?

The closest I have come is to define my own unapply method:

class Player {
  self =>

  var _life = 20
  def life = _life

  def gainLife(life: Int) = execute(GainLife(life))

  case class GainLife(life: Int) extends Event {
    def player = self

    def execute() = _life += life
  }
}

object Player {
  object _GainLife {
    def unapply(event: Player#GainLife) =
      Some((event.player, event.life))
  }
}

Note that naming the Player._GainLife object Player.GainLife instead would cause a name conflict, that is the most important downside here. Therefore, I chose to make that type available from outside the Player namespace:

val GainLife = Player._GainLife

This allows to match using both player.GainLife.unapply and Player._GainLife.unapply in a concise way:

//gain twice as much life
def effect1(player: Player): ReplacementEffect = {
  case player.GainLife(life) => player.GainLife(life * 2)
}

//only you gain life
def effect2(player: Player): ReplacementEffect = {
  case GainLife(_player, life) if _player != player => player.GainLife(life)
}

//all players gain twice as much life
def effect3: ReplacementEffect = {
  case GainLife(player, life) => player.GainLife(life * 2)
}

The last two examples look a little asymmetric, but that can be fixed with an apply method if desired.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM