Reputation: 197
So I have a class that suppose to use generic type, but that type should be with certain characteristics
calculate
Seq[Double]
At the moment I have a trait
trait HasCalculate {def calculate(): Double}
And I use it this way:
val pars = Seq(1.0, 2.0)
val calc = new Calc1(pars) with HasCalculate
val res = calc.calculate
When I want to use another calculator I put Calc2 instead Calc1 in the code of the class. But I would like to do it generic way, something like:
class MyClass[T]{
val pars = Seq(1.0, 2.0)
val calc = new T(pars) with HasCalculate
val res = calc.calculate
}
But how to define that T
has constructor that accepts Seq[Double]
?
Upvotes: 2
Views: 65
Reputation: 197
I came up with an answer myself I would like to share...
So instead MyClass has type parameter it could have a function as parameter, something like this:
class MyClass(f:(Seq[Double])=>HasCalculate){
val pars = Seq(1.0, 2.0)
val calc = f(pars)
val res = calc.calculate
}
And then to provide an anonymous function with the constructor in its body:
val myClass = new MyClass((s:Seq[Double])=>new Calc1(s) with HasCalculate)
Of course this looks ugly but in my case it appears to be more practical than Travis's solution, since I have lots of calculators and I don't intend to create that factory object for each of them or each time I want to run a calculator through MyClass
. I just copy this line of code and replace Calc1
with Calc99
...
So if you have few calculators and lots of calls to MyClass, definitely Trevis's solution is better, otherwise this might be useful...
Upvotes: 0
Reputation: 139038
What you're describing doesn't sound like it's possible in Scala (which doesn't really have facilities for abstracting over constructors), and without knowing your larger goals more specifically it's hard to offer good advice, but the following is a Scala-idiomatic solution that provides the kind of usage it looks like you want for MyClass
and that is specifically designed to let you use generic types while constraining those types to have certain operations.
The first step is to write a type class that captures the operations you need:
trait Calculable[A] {
def create(values: Seq[Double]): A
def calculate(a: A): Double
}
You can think of instances of this type as "evidence" that you can perform these operations on some A
.
Next you'd write your MyClass
like this:
class MyClass[T: Calculable] {
private val instance = implicitly[Calculable[T]]
val pars = Seq(1.0, 2.0)
val calc: T = instance.create(pars)
val res: Double = instance.calculate(calc)
}
The T: Calculable
part is a "context bound", which specifies that there must be implicit evidence that T
has a Calculable
instance. It's a constraint that says "T
can be any type, as long as we know how to do the Calculable
operations on it".
Now you could write a particular class that could be used as T
like this:
class MyCalculation(vs: Seq[Double]) {
def calculate(): Double = vs.sum
}
object MyCalculation {
implicit val calculableInstance: Calculable[MyCalculation] =
new Calculable[MyCalculation] {
def create(values: Seq[Double]): MyCalculation = new MyCalculation(values)
def calculate(a: MyCalculation): Double = a.calculate()
}
}
And you get the usage you want:
scala> val myClass = new MyClass[MyCalculation]
myClass: MyClass[MyCalculation] = MyClass@646bf8a6
scala> myClass.res
res0: Double = 3.0
If you control the definition of MyCalculation
, the most convenient place to define its implicit Calculable[MyCalculation]
is the MyCalculation
companion object, but one of the advantages of the type class approach is that it separates the definition of operations on a type from the definition of the type, and these instances can be defined separately.
Upvotes: 4