问题
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): GError:(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]
回答1:
You probably want
<:
(subtype) instead of:
(context bound).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").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)
}
回答2:
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(_ |+| _)
来源:https://stackoverflow.com/questions/59800677/how-to-restrict-method-parameter-to-subclass-type-in-scala