Reputation: 681
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:
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.)
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:
Scala Overview (2006) http://www.scala-lang.org/docu/files/ScalaOverview.pdf
Martin Kneissl blog (2009) http://www.familie-kneissl.org/Members/martin/blog/family-polymorphism-in-scala
Discussion of approaches to family polymorphism involving typeclasses, macros, f-bounded polymorphism, or anything else are welcome.
Upvotes: 9
Views: 1829
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