0
votes

I'm attempting a simple exercise with shapeless' extensible records.

It's a typeclass called Projection, which should be able to somewhat combine the functionality of Updater and Remover:

import shapeless._
import shapeless.tag._
import shapeless.record._
import shapeless.labelled._
import shapeless.ops.record._
import shapeless.syntax._
// Probably way too many imports

trait Projection[A <: HList, K, V] {
    type B <: HList

    def to(a: A, v: V): B
    def from(b: B): A
}

object Projection {
    type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

    type Key[K] = Symbol with Tagged[K]
    type F[K, V] = V with FieldType[Key[K], V]

    implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
        keyWitness: Witness.Aux[K],
        updater: Updater.Aux[A, F[K, V], B0],
        remover: Remover.Aux[B0, K, (V, A)]
    ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
        type B = B0

        def from(b: B0): A = b - keyWitness
        def to(a: A, v: V): B0 = a + field[Key[K]](v)
    }
}

My rather simple test

import Projection._

val thirdFieldWitness = Witness("thirdField")
val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

unfortunately fails with the error

could not find implicit value for parameter e: Projection[shapeless.HNil,ProjectionSpec.this.thirdFieldWitness.T,Boolean]
[error]         val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

-Xlog-implicits shows the reason for it:

ProjectionSpec.scala:18:35: record.this.Remover.mkRemover is not a valid implicit value for shapeless.ops.record.Remover.Aux[Boolean with shapeless.labelled.FieldType[Projection.Key[ProjectionSpec.this.thirdFieldWitness.T],Boolean] :: shapeless.HNil,ProjectionSpec.this.thirdFieldWitness.T,(Boolean, shapeless.HNil)] because:
[info] hasMatchingSymbol reported error: No field String("thirdField") in record type Boolean with shapeless.labelled.FieldType[Projection.Key[ProjectionSpec.this.thirdFieldWitness.T],Boolean] :: shapeless.HNil
[info]         val projector = implicitly[Projection[HNil, thirdFieldWitness.T, Boolean]]

Please help me understand this message and show me how to fix it.

Is there possibly an easier way to do this kind of extension and shortening of labelled generics?

1

1 Answers

1
votes

Except -Xlog-implicits, one more standard way of debugging implicits is to resolve them manually and look at compile error.

Try

object Projection {
  type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

  type Key[K] = Symbol with Tagged[K]
  type F[K, V] = FieldType[Key[K], V]

  implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
    keyWitness: Witness.Aux[Key[K]],
    updater: Updater.Aux[A, F[K, V], B0],
    remover: Remover.Aux[B0, Key[K], (V, A)]
  ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
    type B = B0

    def from(b: B0): A = b - keyWitness
    def to(a: A, v: V): B0 = a + field[Key[K]](v)
  }
}

Then

implicitly[Projection.Aux[HNil, "thirdField", Boolean, Record.`'thirdField -> Boolean`.T]]

compiles.

But although implicitly[thirdFieldWitness.T =:= "thirdField"]

implicitly[Projection.Aux[HNil, thirdFieldWitness.T, Boolean, Record.`'thirdField -> Boolean`.T]]

still doesn't compile. But manually resolved

implicitly[Projection.Aux[HNil,
  thirdFieldWitness.T,
  Boolean,
  Record.`'thirdField -> Boolean`.T
]](Projection.mkProjection(
  implicitly[Witness.Aux[Witness.`'thirdField`.T]],
  implicitly[Updater.Aux[HNil, FieldType[Witness.`'thirdField`.T, Boolean], Record.`'thirdField -> Boolean`.T]],
  implicitly[Remover.Aux[Record.`'thirdField -> Boolean`.T, Witness.`'thirdField`.T, (Boolean, HNil)]]
))

compiles. The thing seems to be that implicitly[Witness.Aux[Key["thirdField"]]] compiles but implicitly[Witness.Aux[Key[thirdFieldWitness.T]]] doesn't ("Symbol with Tagged[thirdFieldWitness.T] is not a singleton type").

You can fix compilation if you add

implicit def extraWitness[S <: String](implicit 
  w: Witness.Aux[S]
): Witness.Aux[Symbol @@ S] = Witness.mkWitness(tag[S](Symbol(w.value)))

I would use standard Symbol-based API

object Projection {
  type Aux[A <: HList, K, V, B0 <: HList] = Projection[A, K, V] { type B = B0 }

  type F[K, V] = FieldType[K, V]

  implicit def mkProjection[A <: HList, K, V, B0 <: HList](implicit
    keyWitness: Witness.Aux[K],
    updater: Updater.Aux[A, F[K, V], B0],
    remover: Remover.Aux[B0, K, (V, A)]
  ): Projection.Aux[A, K, V, B0] = new Projection[A, K, V] {
    type B = B0

    def from(b: B0): A = b - keyWitness
    def to(a: A, v: V): B0 = a + field[K](v)
  }
}

implicitly[Projection.Aux[HNil, Witness.`'thirdField`.T, Boolean, Record.`'thirdField -> Boolean`.T]]

val thirdFieldWitness = Witness('thirdField)
implicitly[Projection.Aux[HNil, thirdFieldWitness.T, Boolean, Record.`'thirdField -> Boolean`.T]]