3

I have a Scala class with two parameters, and another one parameter constructor. For the one parameter constructor, I call a method to get a tuple of two elements and tried to use the tuple for the parameter of the constuctor that requires two parametes. This is an example:

def vals(v:Int) = {
    // computation
    (v,v) // returns two element tuple
}

class A(a:Int, b:Int) {
    def this(v:Int) = {
        this(vals(v))
    }
}

object Main extends App {
    val a = new A(10)
}

However, I get type mismatch error. I found a solution in scala tuple unpacking that works with function invocation, but not with constructor.

def foo(x: Int, y: Int) = x * y
def getParams = {
    (1,2)  //where a & b are Int
}

object Main extends App {
    println((foo _).tupled(getParams))
    println(Function.tupled(foo _)(getParams))
}

How can solve this issue?

Community
  • 1
  • 1
prosseek
  • 182,215
  • 215
  • 566
  • 871

2 Answers2

5

Create a companion object for the class and add a couple of "factory" ("apply") methods to it, then ditch the extra constructor in favour of the factory methods. You can also include the vals method there to keep things together, if you want (although you can keep it defined elsewhere, too, if that works better for you). Then you end up with something like the following:

class A(val a:Int, val b:Int)

object A {

  def apply(pair: (Int, Int)): A = new A(pair._1, pair._2)

  def apply(v: Int): A = A(vals(v))

  def vals(v:Int) = {
    // computation
    (v,v) // returns two element tuple
  }
}

And create your A with:

scala> val a = A(10)
a: A = A@36d6ec03

scala> a.a
res6: Int = 10

Note that I declared the 'a' and 'b' fields as 'val's. This made them accessible, as in the line a.a above. In fact, I would recommend making A a "case class", eg. case class(a: Int, b: Int) which automatically adds 'val' to the fields, and also creates a companion class for you (with another, default, "apply" method taking two Ints). You also get implementations of toString, equals, and hashcode for free.

Shadowlands
  • 14,994
  • 4
  • 45
  • 43
0

While I concur with and recommend the answer given by Shadowlands, you might encounter a compiler bug in versions prior to Scala 2.12.2 (see SI-3772) prevents defining companion objects for method-owned case classes (which, depending on declaration order, results in error MyClass is already defined as object MyClass or MyClass is already defined as (compiler-generated) case class companion object MyClass).

An unorthodox workaround is to add two constructors to your case class: one accepting the initial value passed to the external function, and another accepting a tuple from your computation which gets unpacked as arguments for the case class' parameters.

def values(i: Int): (Int, String) = i -> i.toString

case class Foo(i: Int, s: String) {
  private def this(values: (Int, String)) = this(values._1, values._2)
  def this(i: Int) = this(values(i))
}

assert(Foo(10, "10") == new Foo(10))

Conspicuously-decorate this with comments referencing to the aforementioned bug.

Community
  • 1
  • 1
Michael Ahlers
  • 616
  • 7
  • 21