【问题标题】:How to restrict method parameter to subclass type in Scala如何在Scala中将方法参数限制为子类类型
【发布时间】:2020-01-19 01:41:04
【问题描述】:

我有一个特征GameStatistics,它定义了一个add() 方法,该方法接受一个参数并返回其自身和参数之和。子类中的实现应该只接受它们自己类型的实例作为参数(或者也可能是子类型)。

我想使用这个add 方法来聚合GameStatistics 的列表,使用Seq 的reduce 方法。

我无法在 Scala 中定义它并使其编译。下面是我尝试的一个示例以及它的编译错误。

这些错误对我来说没有任何意义。我应该如何让它工作?

package bgengine

trait GameStatistics {
  def equity: Double

  def add[G: this.type](s: G): G

  def multiply(x: Double): GameStatistics
}

object GameStatistics {
  def aggregate(stats: Seq[GameStatistics]): GameStatistics = stats.reduce( _ add _ )
}

case class SimpleGameStatistics(equity: Double, nrGames: Int) extends GameStatistics {

  override def add[G: SimpleGameStatistics](s: G): G =
    SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

  override def multiply(x: Double): SimpleGameStatistics = SimpleGameStatistics(equity * x, nrGames)
}

错误:(6, 12) GameStatistics.this.type 不接受类型参数
def add[G: this.type](s: G): G

错误:(17, 21) bgengine.SimpleGameStatistics 不接受类型 参数覆盖 def add[G: SimpleGameStatistics](s: G): G =

错误:(18, 48) 值权益不是类型参数 G 的成员 SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

错误:(18, 59) 值 nrGames 不是类型参数 G 的成员 SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

错误:(18, 83) 值 nrGames 不是类型参数 G 的成员 SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

错误:(18, 105) 值 nrGames 不是类型参数 G 的成员 SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

【问题讨论】:

    标签: scala generics


    【解决方案1】:

    考虑类型类方法

    case class SimpleGameStatistics(equity: Double, nrGames: Int)
    
    trait GameStatistics[G] {
      def add(a: G, b: G): G
      def multiply(x: Double, a: G): G
    }
    
    object GameStatistics {
      implicit val simpleGameStatistics = new GameStatistics[SimpleGameStatistics] {
        def add(a: SimpleGameStatistics, b: SimpleGameStatistics) = SimpleGameStatistics((a.equity * a.nrGames + b.equity + b.nrGames) / (a.nrGames + b.nrGames), a.nrGames + b.nrGames)
        def multiply(x: Double, a: SimpleGameStatistics) = SimpleGameStatistics(a.equity * x, a.nrGames)
      }
    
      implicit class StatsOps[G](private val a: G) {
        def add(b: G)(implicit ev: GameStatistics[G]): G = ev.add(a, b)
        def multiply(x: Double)(implicit ev: GameStatistics[G]): G = ev.multiply(x, a)
      }
      implicit class AggregateOps[G](private val stats: List[G]) {
        def aggregateStats(implicit ev: GameStatistics[G]): G = stats.reduce(_ add _)
      }
    }
    
    
    import GameStatistics._
    SimpleGameStatistics(42, 7) add SimpleGameStatistics(8, 43)
    List(SimpleGameStatistics(42, 7), SimpleGameStatistics(8, 43)).aggregateStats
    SimpleGameStatistics(42, 7) multiply 7
    

    哪个输出

    import GameStatistics._
    res0: SimpleGameStatistics = SimpleGameStatistics(6.9,50)
    res1: SimpleGameStatistics = SimpleGameStatistics(6.9,50)
    res2: SimpleGameStatistics = SimpleGameStatistics(294.0,7)
    

    请注意,这种“加法”二元运算是一种非常常见的模式,cat 为其提供了一个名为 Semigroup 的抽象,因此如果我们为 SimpleGameStatistics 提供 Semigroup 实例

    import cats.Semigroup
    
    implicit val intAdditionSemigroup: Semigroup[SimpleGameStatistics] =
      (a: SimpleGameStatistics, b: SimpleGameStatistics) => SimpleGameStatistics((a.equity * a.nrGames + b.equity + b.nrGames) / (a.nrGames + b.nrGames), a.nrGames + b.nrGames)
    

    我们可以挂钩所有的好东西cats提供开箱即用,例如|+|中缀运算符

    import cats.implicits._
    
    SimpleGameStatistics(42, 7) |+| SimpleGameStatistics(8, 43)
    List(SimpleGameStatistics(42, 7), SimpleGameStatistics(8, 43)).reduce(_ |+| _)
    

    【讨论】:

      【解决方案2】:
      1. 您可能需要<:(子类型)而不是:context bound)。

      2. this.type 并不意味着您认为的意思(它是type which only has this (and null) as value,而不是“当前类型”)。

      3. 如果你解决了这些问题,那么演员表

        override def add[G <: SimpleGameStatistics](s: G): G =
          SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]
        

        没有意义;您刚刚创建了一个 SimpleGameStatistics 的实例,将其转换为子类会引发异常。

      但看起来你想要F-bounded polymorphism

      trait GameStatistics[G <: GameStatistics[G]] { this: G =>
        def equity: Double
      
        def add(s: G): G
      
        def multiply(x: Double): G
      }
      
      object GameStatistics {
        def aggregate[G <: GameStatistics[G]](stats: Seq[G]): G = stats.reduce( _ add _ )
      }
      
      case class SimpleGameStatistics(equity: Double, nrGames: Int) extends GameStatistics[SimpleGameStatistics] {
      
        override def add(s: SimpleGameStatistics): SimpleGameStatistics =
          SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames)
      
        override def multiply(x: Double): SimpleGameStatistics = SimpleGameStatistics(equity * x, nrGames)
      }
      

      【讨论】:

      • 太好了,这就是我想要实现的。我不知道这样的递归泛型是可能的。
      猜你喜欢
      • 1970-01-01
      • 2012-04-06
      • 1970-01-01
      • 1970-01-01
      • 2015-12-26
      • 2019-06-01
      • 1970-01-01
      • 2021-08-10
      • 2020-05-22
      相关资源
      最近更新 更多