Расширяемые типы записей - PullRequest
0 голосов
/ 08 апреля 2020

Я пытаюсь выполнить простое упражнение с бесформенными расширяемыми записями.

Это класс типов, называемый Projection, который должен несколько комбинировать функциональность Updater и 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)
    }
}

Мой довольно простой тест

import Projection._

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

, к сожалению, не удается с ошибкой

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 показывает причину этого:

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]]

Пожалуйста, помогите мне понять это сообщение и покажите, как его исправить.

Возможно, существует более простой способ сделать такое расширение и сокращение обозначенных генериков?

1 Ответ

1 голос
/ 08 апреля 2020

Кроме -Xlog-implicits, еще один стандартный способ отладки последствий - это разрешение их вручную и просмотр ошибки компиляции.

Попробуйте

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)
  }
}

Затем

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

компилируется.

Но хотя implicitly[thirdFieldWitness.T =:= "thirdField"]

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

по-прежнему не компилируется. Но разрешенные вручную компиляции

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)]]
))

. Кажется, дело в том, что implicitly[Witness.Aux[Key["thirdField"]]] компилируется, а implicitly[Witness.Aux[Key[thirdFieldWitness.T]]] нет («Symbol with Tagged[thirdFieldWitness.T] не является одноэлементным типом»).

Вы можете исправить компиляцию, добавив

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

Я бы использовал стандартный 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]]
...