Jas
Jas

Reputation: 15133

in scala how to convert one case class to another immune to code changes field additions?

case class Cat(color: Int, isFat: Boolean)
case class Kitten(color: Int, isFat: Boolean)

I want to construct Kitten from Cat but want to do it automatically without passing all parameters (so if i add more field to Cat / Kitten I would not need to change code). Is there a compact way to achieve that in scala?

val kitten = Cat(1,True).what_to_do_here? // compact & immune to code changes no need to change that line in case of added fields.

Upvotes: 7

Views: 6432

Answers (4)

Kris
Kris

Reputation: 5792

You can do this and much more with Chimney Example:

cat.into[Kitten].transform

or the other way round:

kitten.into[Cat].transform

Upvotes: 1

Johan Walters
Johan Walters

Reputation: 206

You would want to take a look into Shapeless.

import shapeless._
import shapeless.syntax._


case class Cat(color: Int, isFat: Boolean)
case class Kitten(color: Int, isFat: Boolean)

val kitten = Kitten(2, true)

val genCat = Generic[Cat]
val genKit = Generic[Kitten]

val cat: Cat = genCat.from(genKit.to(kitten))

println(cat)

Shapeless is a library for generic programming. For example, the Generic typeclass can convert instances of any hierarchy of sealed traits and case classes (such as Cat and Kitten) into a generic representation (HLists and Coproducts), and back into any compatible class hierarchy. The generic representation in between can be manipulated in a type safe manner.

genKit.to(kitten) takes a Kitten, and produces a HList 2 :: true :: HNil. Since that is compatible with the generic representation of Cat without modification, it can be stored as such using genCat.from.

In this case, Cat and Kitten are nearly identical. What if the order of types was different, or Kitten had an extra property? What if the name of each property is significant? There is tons of useful stuff in Shapeless to easily solve exactly these kind of situations by manipulating the generic representation. Take a look at this example, where some type Book is converted using LabelledGeneric (which uses a HLists with labels, a.k.a. Records), a property is added, and stored into ExtendedBook. All of this type-safe.

Shapeless does use some macros, but seemingly relies only on a minimal set of them. The user of Shapeless does not write any macros him/herself - the heavy lifting is done by Scala's powerful type system.

Upvotes: 16

Przemek Piotrowski
Przemek Piotrowski

Reputation: 7486

Should work, if both classes have same parameters. If one is changed, you will get compiler error.

case class Kitten(color: Int, isFat: Boolean)
case class Cat(color: Int, isFat: Boolean){
  def toKitten = Cat.unapply(this).map(Kitten.tupled).get
}

val kitten = Cat(1,true).toKitten

Upvotes: 3

mohit
mohit

Reputation: 4999

Assuming that the Cat and Kitten will have same fields, you can do

scala> case class Cat(color: Int, isFat: Boolean)
defined class Cat
scala> case class Kitten(color: Int, isFat: Boolean)
defined class Kitten
scala>  Cat(1,true)
res0: Cat = Cat(1,true)
scala> Kitten.tupled(Cat.unapply(res0).get) // This line will remain same 
res1: Kitten = Kitten(1,true)

scala> case class Cat(color: Int, isFat: Boolean, isBrown: Boolean) defined class Cat scala> case class Kitten(color: Int, isFat: Boolean, isBrown: Boolean) defined class Kitten scala> Kitten.tupled(Cat.unapply(Cat(2,true, false)).get) res3: Kitten = Kitten(2,true,false)

Obviously this will not work, if only one of the case classes gets changed arbitarily.

Upvotes: 4

Related Questions