InlineHKDGeneric

perspective provides some special instances of HKDGeneric for other or more specialized purposes. InlineHKDGeneric is one example here, and provides a variant of HKDGeneric to be used in inline functions, and generate nicer and often faster bytecode.

InlineHKDGeneric reimplements all the functions defined in typeclasses on InlineHKDGeneric itself.

InlineHKDProductGeneric uses a lot of beta reduction to simplify code and avoid lambdas in the generated code. At the time of writing InlineHKDGeneric, this beta reduction did not work with polymorphic functions. perspective gets around this by representing the Index type differently using path dependent types. Because of this, use of tabulate functions is heavily recommended when using InlineHKDGeneric Non-tabulate functions are still left in the API but might not generate as good bytecode.

Implicits

Implicit instances of Gen[F] is gotten using the function gen.summonInstances[F] when working with InlineHKDGeneric.

Unrolling

InlineHKDGeneric can in some cases perform loop unrolling if instructed to. Some tabulate functions have another paramete unroll: Boolean = false. Setting this parameter to true will unroll the loop perspective generates. When doing loop unrolling, the code can be specialized based on the current type being worked on. To do this, two extra functions are used, productElementIdExact and lateInlineMatch. productElementIdExact works like productElementId but refines the type when used within an unrolled block. lateInlineMatch is like an inline match, but expands slightly later, after the type of productElementIdExact has been refined.

Example

trait PerspectiveInlineEncoder[A] extends Encoder[A]
object PerspectiveInlineEncoder:

  inline def derivedProductEncoder[A](using gen: InlineHKDProductGeneric[A]): PerspectiveInlineEncoder[A] =
    new PerspectiveInlineEncoder[A]:
      private val names    = gen.names
      private val encoders = gen.summonInstances[Encoder]

      override def apply(a: A): Json =
        val list = gen.tabulateFoldLeft(Nil: List[(String, Json)], unrolling = true)((acc, idx) =>
          val js = gen.lateInlineMatch {
            a.productElementIdExact(idx) match {
              case p: Byte    => Json.fromInt(p)
              case p: Char    => Json.fromString(p.toString)
              case p: Short   => Json.fromInt(p)
              case p: Int     => Json.fromInt(p)
              case p: Long    => Json.fromLong(p)
              case p: Float   => Json.fromFloatOrString(p)
              case p: Double  => Json.fromDoubleOrString(p)
              case p: Boolean => Json.fromBoolean(p)
              case p: String  => Json.fromString(p)
              case other      => encoders.indexK(idx)(other)
            }
          }

          (names.indexK(idx), js) :: acc
        )

        Json.obj(list: _*)

  inline def derived[A](using gen: InlineHKDGeneric[A]): PerspectiveInlineEncoder[A] = inline gen match
    case gen: InlineHKDProductGeneric.Aux[A, gen.Gen] => derivedProductEncoder(using gen)