45

Given:

case class Foo(a: Int, b: String, c: Double)

you can say:

val params = Foo(1, "bar", 3.14).productIterator.toList

and get:

params: List[Any] = List(1, bar, 3.14)

Is there a way to "go backwards" and recreate a Foo object directly from this list, i.e.:

Foo.createFromList(params)   // hypothetical

instead of writing:

Foo(params(0).asInstanceOf[Int], params(1).asInstanceOf[String], params(2).asInstanceOf[Double])

EDIT: it seems that it boils down to being able to send the elements of a list as parameters to a function without writing them out explicitly, e.g.:

def bar(a: Int, b: Int, c: Int) = //...
val list = List(1, 2, 3, 4, 5)
bar(list.take(3)) // hypothetical, instead of:
bar(list(0), list(1), list(2))

I would sort of expect to be able to do:

bar(list.take(3): _*)

but that doesn't seem to work.

EDIT: Solution based on extempore's answer, but invoking the constructor directly instead of using the apply method:

case class Foo(a: Int = 0, b: String = "bar", c: Double = 3.14) {
    val cs = this.getClass.getConstructors
    def createFromList(params: List[Any]) =
    cs(0).newInstance(params map { _.asInstanceOf[AnyRef] } : _*).asInstanceOf[Foo]
}

Now you can do:

scala> Foo().createFromList(List(4, "foo", 9.81))
res13: Foo = Foo(4,foo,9.81)

You can also refactor the creation method into a trait:

trait Creatable[T <: Creatable[T]] {
    val cs = this.getClass.getConstructors
    def createFromList(params: List[Any]) =
        cs(0).newInstance(params map { _.asInstanceOf[AnyRef] } : _*).asInstanceOf[T]   
}

case class Bar(a: Int = 0, b: String = "bar", c: Double = 3.14) extends Creatable[Bar]

And do e.g.:

scala> val bar = Bar()
bar: Bar = Bar(0,bar,3.14)

scala> bar == bar.createFromList(bar.productIterator.toList)
res11: Boolean = true
Knut Arne Vedaa
  • 15,372
  • 11
  • 48
  • 59
  • doing some fast benchmarking you can find that just pasting manually all the values, e.g. Foo(list(0), list(1), list(2)) it's x10 faster. Testing on a case class which has 43 values. – LowFieldTheory Sep 24 '17 at 18:31

4 Answers4

57
scala> case class Foo(a: Int, b: String, c: Double)
defined class Foo

scala> val params = Foo(1, "bar", 3.14).productIterator.toList
params: List[Any] = List(1, bar, 3.14)

scala> Foo.getClass.getMethods.find(x => x.getName == "apply" && x.isBridge).get.invoke(Foo, params map (_.asInstanceOf[AnyRef]): _*).asInstanceOf[Foo]
res0: Foo = Foo(1,bar,3.14)

scala> Foo(1, "bar", 3.14) == res0
res1: Boolean = true

Edit: by the way, the syntax so far only being danced around for supplying the tuple as an argument is:

scala> case class Foo(a: Int, b: String, c: Double)
defined class Foo

scala> Foo.tupled((1, "bar", 3.14))                
res0: Foo = Foo(1,bar,3.14)
psp
  • 12,138
  • 1
  • 41
  • 51
  • That seems to be an actual solution. How much overhead does the use of reflection create? (And what is the significance of the isBridge check?) – Knut Arne Vedaa Nov 28 '10 at 10:58
  • 2
    isBridge is just a cheap way to pick the right method. There's another one with the same name which takes (Int, String, Double) instead of (AnyRef, AnyRef, AnyRef) and that call might not go so well. I can't answer the overhead question in any meaningful way. "Some." – psp Nov 28 '10 at 22:43
  • 1
    You can also invoke the constructor directly. I've edited my question with an example of this. – Knut Arne Vedaa Nov 29 '10 at 10:55
15

You could use pattern matching like:

params match {                                   
 case List(x:Int, y:String, d:Double) => Foo(x,y,d)
}
bjartek
  • 929
  • 1
  • 5
  • 11
15

Well, you can certainly do this with a tuple:

(Foo _).tupled apply (1, bar, 3.14)

But there is no real way to get from a List[S] to (A, B, C) for A, B, C <: S. There may be a way of doing this with HLists of course

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 5
    That won't actually work! The `Foo` companion singleton isn't actually a `Function`, it's just a class with an `apply` method. `(Foo.apply _).tupled` should do the trick though. – Kevin Wright Nov 27 '10 at 13:16
  • 1
    It's not? "public final class Foo$ extends scala.runtime.AbstractFunction3 implements scala.ScalaObject,java.io.Serializable" – psp Nov 28 '10 at 05:17
  • @Kevin - thanks, I am pretty sure that I have not had to declare the `apply` explicitly in my own code – oxbow_lakes Nov 28 '10 at 18:38
  • @KevinWright it is when there's no type parameters involved: http://stackoverflow.com/questions/25345211/tupled-method-for-case-class-having-a-type-parameter – Gabriele Petronella Aug 17 '14 at 01:15
1

Another one liner using case class companion object curried method and completely ignoring type safety :)

scala> case class Foo(a: Int, b: String, c: Double)
defined class Foo

scala> val lst = List(1, "bar", 3.14)
lst: List[Any] = List(1, bar, 3.14)

scala> val foo = lst.foldLeft(Foo.curried: Any){case (r, v) => r.asInstanceOf[Function[Any, _]](v) }.asInstanceOf[Foo]
foo: Foo = Foo(1,bar,3.14)
Miquel
  • 4,741
  • 3
  • 20
  • 19