简体   繁体   中英

How to restrict method parameter to subclass type in Scala

I have a trait GameStatistics that defines an add() method that takes a parameter and returns the sum of itself and the parameter. Implementations in subclasses should only accept instances of their own type as a parameter (or maybe also subtypes).

I would like to use this add method to aggregate lists of GameStatistics , using Seq's reduce method.

I have not been able to define this in Scala and make it compile. Below is one example that I tried plus its compile errors.

The errors don't make any sense to me. How should I get this to work?

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

Error:(6, 12) GameStatistics.this.type does not take type parameters
def add[G: this.type](s: G): G

Error:(17, 21) bgengine.SimpleGameStatistics does not take type parameters override def add[G: SimpleGameStatistics](s: G): G =

Error:(18, 48) value equity is not a member of type parameter G SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

Error:(18, 59) value nrGames is not a member of type parameter G SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

Error:(18, 83) value nrGames is not a member of type parameter G SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

Error:(18, 105) value nrGames is not a member of type parameter G SimpleGameStatistics((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames).asInstanceOf[G]

Consider typeclass approach

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

which outputs

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

Note these sort of "addition" binary operations are a very common pattern for which cats provides an abstraction called Semigroup , hence if we provide Semigroup instance for SimpleGameStatistics

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)

we can hook into all the goodies cats provides out-of-the-box such as |+| infix operator

import cats.implicits._

SimpleGameStatistics(42, 7) |+| SimpleGameStatistics(8, 43)
List(SimpleGameStatistics(42, 7), SimpleGameStatistics(8, 43)).reduce(_ |+| _)
  1. You probably want <: (subtype) instead of : ( context bound ).

  2. this.type doesn't mean what you think it means (it is the type which only has this (and null ) as value , not "the current type").

  3. If you fixed those problems, the cast in

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

    wouldn't make sense; you just created an instance of SimpleGameStatistics , casting it to a subclass would throw an exception.

But it looks like you want 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)
}

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