Skip to content

Conversation

@arainko
Copy link
Owner

@arainko arainko commented Dec 10, 2022

  • resolve an issue where implicit resolution in a macro (eg. Expr.summon and Implicits.search) was failing and a fallback to scala.compiletime.summonInline was needed

A solution to this issue was to create a macro that creates the typeclass instance itself not a macro that only does the underlying transformation.
Eg.

// OG version
inline def transform[A, B](value: A): B = ${ transformMacro[A, B]('value) }
def transformMacro[A: Type, B: Type](value: Expr[A])(using Quotes): Expr[B] = ??? // do the transformation here

inline given Transformer[A, B] = (a: A) => transform[A, B](a) // <-- this seems to trip implicit search with Expr.summon/Implicits.search but not summonInline

// fixed version
inline def transformer[A, B]: Transformer[A, B] =  ${ transformerMacro[A, B]('value) }
def transformerMacro[A: Type, B: Type](using Quotes): Expr[Transformer[A, B] = ??? // do the transformation but also build an instance of the typeclass at the same time

inline given Transformer[A, B] =  transformer[A, B] // <-- this doesn't trip Expr.summon/Implicits.search - weird weird weird 

This fix allowed me to greatly simplify logic of Transformer normalization, which can now be done straight after getting our hands on an instance of a transformer instead of going through the whole tree to transform some specific nodes after (summonInline expands some time after the initial inlining?)

Examples:

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: Option[EvenMoreInside2])

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

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

person.to[Person2] now expands to:

to[Person](person)[Person2]((inline$make$i1[Person, Person2](ForProduct)((((source: Person) =>
 new Person2(
    int = source.int,
     str = source.str,
     inside = new Inside2(
      int = source.inside.int,
      str = source.inside.str,
      inside = Some.apply[EvenMoreInside2](
        new EvenMoreInside2(
          str = source.inside.inside.str,
          int = source.inside.inside.int
        )
      )
 ))): Transformer[Person, Person2])): ForProduct[Person, Person2]))

Note how inside expands to a Some(...), in the previous version this call would allocate two more instances of Transformer and then do the transformation, now it does the transformation without any extra allocations.

It also rewrites collection-to-collection transformation to

val transformedCollection = sourceCollectio.map(src => ... /* transform from src to dest */).to(destCollectionFactory)

Just as in the Some(...) example, no intermediate transformers are needed anymore!

I also plan to special case invocations of to do rewrite the AST to contain the transformation lifted from the supplied transformer so in some cases the library will be able to get rid of an additional transformer instance.

@arainko arainko merged commit 6860f36 into master Dec 10, 2022
@arainko arainko deleted the rework-derivation branch December 29, 2022 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants