I need a function to accept any case class which extends a trait. To give an example
Suppose there is a trait
trait Action {
def execute(param: Parameters)
}
And following parameters can be passed depending on what type of action it is
trait Parameters
case class A(x: String, y: Double) extends Parameters
case class B(x: Int, y:String, z: Double) extends Parameters
User should be able to specify what it wants as parameters
class Action1 extends Action {
override def execute(param: A)
}
class Action2 extends Action {
override def execute(param: B)
}
and while calling
def run(t: Action) {
t.execute(new A("a", 1.0))
}
How to achieve this? When I implement Action1, it says I will get error that we cannot override because trait says execute method accepts Parameters, but while overriding, we want a class which extends Parameters.
One way is to parametrize Action
.
trait Action[T <: Parameters] {
def execute(param: T)
}
Then
class Action1 extends Action[A] {
override def execute(param: A)
}
class Action2 extends Action[B] {
override def execute(param: B)
}
def run[T <: Parameters](t: Action[T], param: T) {
t.execute(param)
}
// You don't need new for a case class
run(new Action1, A("a", 1.0))
You have to modify run
a little bit here because you can't get what you're asking for by subclassing alone. Argument types are contravariant, meaning that something that a function A => T
is a superclass of a function Parameters => T
. This is because while any argument of type A
works for both A => T
and Parameters => T
, only some arguments of type Parameters
will work for A => T
while all of them will work for Parameters => T
.
The thing you need to think about is why do you need your Action
to have the execute
method to begin with. Probably, you have something like this elsewhere in your code:
def executeAll(actions: Seq[Action], params: Parameters) =
actions.foreach { _.execute(params) }
Note, that here your actions
list can contain actions of different types. if some of them except the parameter of type A
, and some need it to be of type B
, it won't work, because your params
parameter cannot be both.
For this reason, you cannot override a method to take a different parameter type in a subclass (actually, you can, but the type has to be a super-class of the original type, not a subclass. They say that functions are contravariant in their parameter types for this reas, but I digress).
If your Action1
and Action2
can actually deal with their parameters generically, through the trait interface without requiring them to be of a particular type, then the solution is simple: just change the declarations of your overriding methods to be override def execute(params: Parameters)
.
If this is not the case, and Action1
can only deal with parameters of type A
, then (1) there is, probably (although, not necessarily) something wrong with your design, and (2) you have to go with parametrization solution, as the other answer suggests. But then you would have to change all the places, that deal with Action
as well:
def executeAll[T <: Parameters](actions: Seq[Action[T]], params: T) = ...
This declaration constraints the type of the params
argument to match what the actions in the list expect, so that Action1.execute
will not be called with B
by mistake.
Now, as you mentioned in the comment to the other answer, you can't mix actions of different type, because it does not make sense: they are essentially different classes. If you are not going to use the execute
method, and just want to pass the list of different actions around some piece of code, you can use the wildcard syntax:
val actions: List[Action[_] = List(Action1(...), Action2(...))
Once again, this list can serve as a generic container for actions of any type, but you cannot use it to apply the execute
method generically, because that would require the parameter to be of different types at the same time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With