Djura Marinkov
Djura Marinkov

Reputation: 197

Is it possible to specify that a type will have a certain parameters in its constructor? Scala

So I have a class that suppose to use generic type, but that type should be with certain characteristics

  1. It needs to have defined method calculate
  2. It needs to have constructor that accepts 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

Answers (2)

Djura Marinkov
Djura Marinkov

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

Travis Brown
Travis Brown

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

Related Questions