Как ограничить параметр метода типом подкласса в Scala - PullRequest
0 голосов
/ 18 января 2020

У меня есть черта GameStatistics, которая определяет метод add(), который принимает параметр и возвращает сумму себя и параметра. Реализации в подклассах должны принимать только экземпляры своего собственного типа в качестве параметра (или, возможно, также подтипы).

Я хотел бы использовать этот метод add для агрегирования списков GameStatistics, используя Seq's reduce method.

Мне не удалось определить это в 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 не принимает параметры типа override def add [G: SimpleGameStatistics] (s: G): G =

Ошибка : (18, 48) значение Equity не является членом параметра типа 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 SimpleGameStat istics ((equity * nrGames + s.equity * s.nrGames) / (nrGames + s.nrGames), nrGames + s.nrGames) .asInstanceOf [G]

Ответы [ 2 ]

3 голосов
/ 18 января 2020

Рассмотрим подход типа типов

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)

Обратите внимание, что этот вид бинарных операций "сложения" является очень распространенным шаблоном, для которого коты предоставляют абстракцию под названием Semigroup, следовательно, если мы предоставим Semigroup экземпляр для 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)

, мы сможем подключиться ко всем вкусностям cats предоставляет готовые решения, такие как |+| инфиксный оператор

import cats.implicits._

SimpleGameStatistics(42, 7) |+| SimpleGameStatistics(8, 43)
List(SimpleGameStatistics(42, 7), SimpleGameStatistics(8, 43)).reduce(_ |+| _)
2 голосов
/ 18 января 2020
  1. Вы, вероятно, хотите <: (подтип) вместо : ( привязка к контексту ).

  2. this.type не не означает, что вы думаете, что это означает (это тип , который имеет thisnull) в качестве значения , а не «текущий тип»).

  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-ограниченный полиморфизм :

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)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...