Adam Pingel
Adam Pingel

Reputation: 681

Family Polymorphism in Scala

What is the current recommended pattern for family polymorphism in Scala?

While experimenting with ways of modeling games, this solution recently emerged:

trait Game[G <: Game[G]] {

  type PLAYER <: Player[G]
  type STATE <: State[G]

  def players(): Set[G#PLAYER]

  def startState(): G#STATE
}

trait Player[G <: Game[G]]

trait State[G <: Game[G]] {
  def player(): G#PLAYER
}

A specific game (Poker in this example) can be expressed in terms of those traits like so:

class Poker() extends Game[Poker] {

  type PLAYER = PokerPlayer
  type STATE = PokerState

  val p1 = new PokerPlayer()

  def players() = Set(p1)

  def startState(): PokerState = ...
}

class PokerPlayer() extends Player[Poker]

class PokerState() extends State[Poker] {
  def player(): PokerPlayer = ...
}

I have several questions about this setup:

  1. How is Game[G <: Game[G]] pronounced in English? What are the names for the roles that G and Game are playing in this situation? (Meaning specifically in this "recursive" relationship.)

  2. Is this a reasonable implementation of "family polymorphism"? At a high level, my understanding is that this means that Game and it's PLAYER and STATE must vary as a "family". The takes on family polymorphism in Scala that I've seen elsewhere differ substantially, and I'm not clear on the different tradeoffs:

  3. Discussion of approaches to family polymorphism involving typeclasses, macros, f-bounded polymorphism, or anything else are welcome.

Upvotes: 9

Views: 1829

Answers (1)

tixxit
tixxit

Reputation: 4122

I would question the need to define the classes "outside" of the trait at all. The types Player and State are already dependent on Game, so you don't need to try to restrict it further.

trait Game {
  type Player
  type State <: StateLike

  trait StateLike {
    def player: Player
  }

  def startState: State
}

class Poker extends Game {
  class Player
  class State extends StateLike { ... }
  val startState = new State
}

You can use the cake pattern to separate different parts out to different traits/files if you want.

trait PokerPlayer extends Game {
  class Player
}
trait PokerState extends Game with PokerPlayer {
  class State extends StateLike { ... }
}
class Poker extends Game with PokerPlayer with PokerState {
  val startState = ...
}

Upvotes: 2

Related Questions