arainko / ducktape

Automatic and customizable compile time transformations between similar case classes and sealed traits/enums, essentially a thing that glues your code. Scala 3 only. Or is it duct 🤔

Home Page:https://arainko.github.io/ducktape/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optimize the number of `Transformer` instances during product transformations

arainko opened this issue · comments

example:

import io.github.arainko.ducktape.*
import io.github.arainko.ducktape.Transformer.ForProduct

case class Person(int: Int, str: String, inside: Inside)
case class Person2(int: Int, str: String, inside: Inside2)

case class Inside(str: String, int: Int, inside: EvenMoreInside)
case class Inside2(int: Int, str: String, inside: EvenMoreInside2)

case class EvenMoreInside(str: String, int: Int)
case class EvenMoreInside2(str: String, int: Int)

val transformed =  Person(1, "2", Inside("2", 1, EvenMoreInside("asd", 3))).to[Person2]

In the current version the generated code is kind of allocation heavy in regards to transformers, let's take a look at what is generated (Output of DebugMacros.code):

to[Person](Person.apply(1, "2", Inside.apply("2", 1, EvenMoreInside.apply("asd", 3))))[Person2](
    (((from: Person) => 
      (new Person2(
        int = from.int,
        str = from.str,
        inside = (
          ((`fromâ‚‚`: Inside) => 
            (new Inside2(
              int = `fromâ‚‚`.int,
              str = `fromâ‚‚`.str, 
              inside = (
                ((`from₃`: EvenMoreInside) => 
                  (new EvenMoreInside2(
                    str = `from₃`.str,
                    int = `from₃`.int
                  ): EvenMoreInside2)): ForProduct[EvenMoreInside, EvenMoreInside2]
              ).transform(`fromâ‚‚`.inside)
            ): Inside2)): ForProduct[Inside, Inside2]).transform(from.inside)): Person2)): ForProduct[Person, Person2])
    )

We can see that for each 'sub-transformation' we allocate a new transformer to then just call transform and get the result, we can simplify it by extracting the inside of the Transformer lambda and calling it directly, so after optimizations this code should look like this:

to[Person](Person.apply(1, "2", Inside.apply("2", 1, EvenMoreInside.apply("asd", 3))))[Person2]((((from: Person) =>
    (new Person2(
      int = from.int,
      str = from.str,
      inside = new Inside2(
        int = from.inside.int,
        str = from.inside.str,
        inside = new EvenMoreInside2(
          str = from.inside.inside.str,
          int = from.inside.inside.int
        )
      )
    ): Person2)
  ): ForProduct[Person, Person2]))

So pretty much something we'd write by hand, this optimization can also be done on ToAnyVal and FromAnyVal transformers.