Reputation: 714
Let's say we have some kinds of widgets, and each widget can have some actions that it can perform. Some actions can be performed by all widgets, and some can be performed by only one kind of widget.
In code, that would look something like this:
trait Widget[T] { def actions: List[Action[T]] }
case class WidgetA(actions: List[Action[WidgetA]]) extends Widget[WidgetA]
case class WidgetB(actions: List[Action[WidgetB]]) extends Widget[WidgetB]
case class WidgetC(actions: List[Action[WidgetC]]) extends Widget[WidgetC]
trait Action[-T <: Widget]
So an Action[Widget]
is a subtype of Action[WidgetA]
, and can be inserted into a WidgetA
's list of actions. So this is valid:
case class UniversalAction() extends Action[Widget]
WidgetA(List(UniversalAction()))
Now, we want to extend the system such that Action
s can be performed by more than one kind of Widget
, but not all the kinds of Widget
, i.e. an Action
may be performed by WidgetA
and WidgetB
, but not WidgetC
. So what I want is to be able to say something like the following:
case class RandomAction() extends Action[WidgetA] with Action[WidgetB]
and have it be considered as both an Action[WidgetA]
and an Action[WidgetB]
, such that the following 2 expressions are valid:
val xs: List[Action[WidgetA]] = List(RandomAction())
val ys: List[Action[WidgetB]] = List(RandomAction())
But we can't extend Action
twice, whether directly or indirectly, so what would be the right way of doing this?
Note: While it may be possible to insert traits into the hierarchy such that it covers some subset of the widgets, that quickly becomes unwieldy when new widget types come into play, hence I would like to avoid doing that.
P.S. The question title isn't the best at the moment, if anyone can suggest a more accurate and descriptive title, I'd be happy to change it.
Upvotes: 2
Views: 235
Reputation: 16298
Typeclasses, i.e. implicit vals and conversions could serve as more rich type relation model than traits. I made an attempt to implement similar logic with implicit conversions.
Suppose we have more little bit more business logic here:
type CanCopyActions[A <: Widget[A]] = {
def copy(actions: List[Action[A]]): A
}
trait Widget[A <: Widget[A]] {
self: A with CanCopyActions[A] =>
def actions: List[Action[A]]
def add[B](action: B)(implicit conv: B => Action[A]) = copy(conv(action) :: actions)
def runall = actions foreach (_(self))
}
trait Action[-A <: Widget[_]] {
def apply(w: A) {}
}
defining our conrete types:
case class WidgetA(actions: List[Action[WidgetA]]) extends Widget[WidgetA]
case class WidgetB(actions: List[Action[WidgetB]]) extends Widget[WidgetB]
case class WidgetC(actions: List[Action[WidgetC]]) extends Widget[WidgetC]
object MyActionA extends Action[WidgetA] {
override def apply(w: WidgetA) = println("action A")
}
object MyActionB extends Action[WidgetB] {
override def apply(w: WidgetB) = println("action B")
}
object MyActionC extends Action[WidgetC] {
override def apply(w: WidgetC) = println("action C")
}
object MyAction extends Action[Widget[_]] {
override def apply(w: Widget[_]) = println("common action")
}
Any type is implicitly convertible to itself, so Widget.add
will work with ad-hoc Action
s, but let's define something interesting:
case class \/[A <: Widget[A], B <: Widget[B]](widget: Either[A, B], actions: List[Action[A \/ B]]) extends Widget[A \/ B] {
def copy(actions: List[Action[A \/ B]]) = \/(widget, actions)
}
implicit def eitherLeftAction[A <: Widget[A], B <: Widget[B]](action: Action[A \/ B]): Action[A] = new Action[A] {
override def apply(widget: A) = action.apply(\/(Left(widget), Nil))
}
implicit def eitherRightAction[A <: Widget[A], B <: Widget[B]](action: Action[A \/ B]): Action[B] = new Action[B] {
override def apply(widget: B) = action.apply(\/(Right(widget), Nil))
}
and special action for this:
object MyActionAB extends Action[WidgetA \/ WidgetB] {
override def apply(w: WidgetA \/ WidgetB) = w.widget match {
case Left(widgetA) => println("dual action A")
case Right(widgetB) => println("dual action B")
}
}
with all above we could run following and see appropriate results:
WidgetA(List(MyActionA)).add(MyAction).add(MyActionAB).runall
WidgetB(List(MyActionB)).add(MyAction).add(MyActionAB).runall
following however will not compile:
WidgetC(List(MyActionC)).add(MyAction).add(MyActionAB).runall
Upvotes: 1