【问题标题】:Scala: How to pattern-match the enclosing object of an inner case class?Scala:如何对内部案例类的封闭对象进行模式匹配?
【发布时间】:2015-02-10 10:36:23
【问题描述】:

我有一个内部案例类,特别是来自this 问题的事件,并且想要匹配它,包括外部对象:

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

我可以轻松地编写一个效果(部分函数)来替换特定玩家的生活事件:

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

但是,我不能对其他玩家做同样的事情。我最接近的是:

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

但是 1) 这甚至会用新的生命增益替换您自己的生命增益,2) 我无法引用最初在函数中获得生命的玩家,以及 3) 我错过了以这种方式直接匹配 life 的机会。

这可以使用与路径无关的类型来表达,例如

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

理想情况下,我想要类似的东西

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

这是否可能,或者我可以解决这个问题吗?还是我必须求助于让 GainLife 嵌套?

【问题讨论】:

    标签: scala pattern-matching case-class


    【解决方案1】:

    当您在另一个内部定义类时,这意味着该类型是特定于周围类的,因此如果您希望它意味着 playerA.GainLife 与 playerB.GainLife 类型不同(这称为路径相关类型)无论实例如何,您都在相同的范围内定义它:包或类的伴随对象。

    您可以在这个问题中阅读更多内容:What is meant by Scala's path-dependent types?

    【讨论】:

    • 我想我知道你要朝哪个方向发展,但这并不是我想要的。我对GainLife 很满意,这取决于受影响的Player,我只想在模式匹配时使用该播放器。我已经添加了我认为您对问题的建议,但这是我想避免的,因为我认为 GainLife 依赖于路径实际上更干净,这将是添加伴随对象的唯一原因。跨度>
    • 我找到了一种我觉得相当满意的方法,如果您有任何改进意见,您介意提出一些改进吗?
    • 这一切对我来说有点奇怪,但也许我只是不太明白你想要达到的目标。如果路径相关类型强制您创建自定义特殊 unnapply,我可能只会创建一个不依赖于路径的 GainLife 事件,并添加所有需要的数据而不是这个。猜猜这是品味的问题。
    • 我同意这很奇怪,但它与我得到的问题的直接答案一样接近,所以我还是想分享它。不过有一些好处:可以访问private[this]字段,不需要在execute()方法中写player.,或者在创建事件时写this。我觉得我的变体有点罗嗦,但只在一个位置(伴生对象)。客户端代码,包括效果,gainLife()execute(),对我来说看起来更好。但我可能想多了这里的重要性。
    【解决方案2】:

    我最接近的是定义我自己的unapply 方法:

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

    请注意,将Player._GainLife 对象命名为Player.GainLife 会导致名称冲突,这是这里最重要的缺点。因此,我选择在 Player 命名空间之外提供该类型:

    val GainLife = Player._GainLife
    

    这允许以简洁的方式同时使用 player.GainLife.unapply 和 Player._GainLife.unapply 进行匹配:

    //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)
    }
    

    最后两个示例看起来有点不对称,但如果需要,可以使用apply 方法修复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-11
      • 2017-02-25
      • 2020-11-28
      • 1970-01-01
      • 1970-01-01
      • 2018-09-13
      • 1970-01-01
      • 2017-04-25
      相关资源
      最近更新 更多