Построить, используя Shapeless, generic c экземпляры по умолчанию для классов дел с параметрами, определяющими общий метод createValue - PullRequest
1 голос
/ 09 апреля 2020

Я пытаюсь добиться следующего - и использование бесформенного выглядит хорошим путем.

Учитывая текущую модель класса:

import shapeless._

object ShapelessTest {
  case class Definition[T](id: String) extends Typeable[T] {
    type V = Value[T]

    override def cast(t: Any): Option[T] = createValue(t.asInstanceOf[Option[T]]).value

    override def describe: String = s"$id"

    def createValue(value: Option[T]): V =
      Value[T](this, value)
  }

  case class Value[T](definition: Definition[T], value: Option[T])

  val DefA: Definition[Int] = Definition[Int]("defA")
  val DefB: Definition[String] = Definition[String]("defB")

  case class Instance(valA: DefA.V,
                      valB: DefB.V)

  def main(args: Array[String]): Unit = {
     val Empty: Instance = Instance(
       DefA.createValue(None),
       DefB.createValue(None)
     )
    println(s"Empty manual: $Empty")

    val emptyHl = Generic[Instance].from(DefA.createValue(None) :: DefB.createValue(None) :: HNil)
    println(s"Empty hlist: $emptyHl")
  }
}

Я могу создать пустой экземпляр как Empty экземпляр, вручную вызывая методы createValue для правильных определений, или путем преобразования HList с использованием бесформенного.

Я пытаюсь выяснить, возможно ли программно создать экземпляр Empty для каждого класса, имеющего поля типа Value.

Другими словами, я хотел бы иметь возможность вызывать

val Empty: Instance = empty[Instance]

и иметь тот же результат, что и emptyHl или Empty instance.

Это похоже на пример "8.3 Генератор случайных значений" в бесформенном руководстве, но вместо генерации случайных чисел, используя функции для каждого типа, присутствующего в классе case, я пытаюсь материализовать конкретный тип Definition для каждого параметра и вызвать для него метод createValue(None).

Я безуспешно пытался.

Использование hlist.Mapper с * 1 028 * определено для Typeable, я могу получить список параметров, но я не могу вызвать какие-либо методы для набираемого текста.

Любая помощь будет принята с благодарностью, спасибо! :)


Update 9 Apr

Я смог придумать очень запутанное решение - к сожалению, много кастинга, но делает свою работу.

Я бы хотел бы повторить это и сделать это лучше. Я пытался использовать natMapper: NatTRel, но я не смог заставить его работать с Singleton Types. Я уверен, что это может быть сделано намного лучше, хотя! Любое предложение приветствуется.

import shapeless.ops.hlist
import shapeless.ops.hlist.{Comapped, Reify}
import shapeless.{Generic, HList, HNil}

object ShapelessTest2 {

  case class Definition[T](id: String) {
    type V = Value[this.type]

    def createValue(value: Option[T]) =
      new Value[this.type] {
        type NT = T
        override val valueT: Option[T] = value
        override val attrDef: Definition.this.type = Definition.this
      }
  }

  trait Value[D] {
    type NT
    val attrDef: D
    val valueT: Option[NT]
  }

  object DefA extends Definition[Int]("defA")
  object DefB extends Definition[Int]("defB")
  object DefC extends Definition[String]("defC")

  case class Instance(valA: DefA.V,
                      valB: DefB.V,
                      valC: DefC.V)

  // Compile safe
  val Inst1: Instance = Instance(
    DefA.createValue(Some(1)),
    DefB.createValue(Some(2)),
    DefC.createValue(Some("2"))
  )

  def main(args: Array[String]): Unit = {
    def empty[A <: Product] = new PartiallyApplied[A]

    class PartiallyApplied[A <: Product] {
      def apply[
          V <: HList,
          DL <: HList,
          RDL <: HList,
          H <: Definition[_],
          T <: HList
      ]()(
          implicit
          gen: Generic.Aux[A, V],
          comapped: Comapped.Aux[V, Value, DL],
          reify: Reify.Aux[DL, RDL],
          isHCons: hlist.IsHCons.Aux[RDL, H, T],
      ): A = {
        def getEmpties[L](list: RDL): V = {
          val hlist = list match {
            case HNil => HNil
            case _ => list.head.createValue(None) :: getEmpties(list.tail.asInstanceOf[RDL])
          }
          hlist.asInstanceOf[V]
        }

        val empties = getEmpties(reify.apply())
        gen.from(empties)
      }
    }

    val emptyInstance = empty[Instance]()
    println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}")
    println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}")
    println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}")
  }
}

Правильно печатает

Empty valA: Definition(defA) - None
Empty valB: Definition(defB) - None
Empty valC: Definition(defC) - None

Ответы [ 2 ]

2 голосов
/ 09 апреля 2020

Полагаю, вы как-то оскорбляете Typeable. Идея использования Typeable состоит в том, чтобы иметь типобезопасное приведение. Но вы возвращаетесь к asInstanceOf.

Typeable - это класс типов. Поэтому вы должны использовать ваш Definition как класс типов. Сделайте DefA, DefB, ... неявным.

implicit val DefA: Definition[Int] = Definition[Int]("defA")
implicit val DefB: Definition[String] = Definition[String]("defB")

def empty[A <: Product] = new PartiallyApplied[A]

class PartiallyApplied[A <: Product] {
  def apply[Vs <: HList, L <: HList, Ds <: HList]()(implicit
    gen: Generic.Aux[A, Vs],
    comapped: Comapped.Aux[Vs, Value, L],
    liftAll: LiftAll.Aux[Definition, L, Ds],
    natMapper: NatTRel[Ds, Definition, Vs, Value],
  ): A = {
    object createValueNatTransform extends (Definition ~> Value) {
      override def apply[T](definition: Definition[T]): Value[T] =
        definition.createValue(None)
    }

    gen.from(natMapper.map(createValueNatTransform, liftAll.instances))
  }
}

val Empty: Instance = empty[Instance]() 
// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))

Макрос работает с вашим исходным кодом

def empty[A]: A = macro emptyImpl[A]

def emptyImpl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
  import c.universe._
  val typA = weakTypeOf[A]
  val trees = typA.decls.filter(_.asTerm.isVal).map(_.infoIn(typA) match {
    case TypeRef(pre, _, _) => q"${pre.termSymbol}.createValue(_root_.scala.None)"
  })
  q"new $typA(..$trees)"
}

val Empty: Instance = empty[Instance]

//Warning:scalac: performing macro expansion App.empty[App.Instance]
//Warning:scalac: new App.Instance(DefA.createValue(_root_.scala.None),
//                                 DefB.createValue(_root_.scala.None))

// Instance(Value(Typeable[defA],None),Value(Typeable[defB],None))

Относительно вашего нового кода make Value ковариантный и использовать Mapper (для правильно определенного Poly) вместо NatTRel или рекурсии во время выполнения

trait Value[+D] {
  type NT
  val attrDef: D
  val valueT: Option[NT]
}

object createValuePoly extends Poly1 {
  implicit def cse[D <: Definition[T] with Singleton, T](implicit
    ev: D <:< Definition[T]): Case.Aux[D, Value[D]] = at(_.createValue(None))
}

def empty[A <: Product] = new PartiallyApplied[A]

class PartiallyApplied[A <: Product] {
  def apply[
      V <: HList,
      DL <: HList,
  ]()(
      implicit
      gen: Generic.Aux[A, V],
      comapped: Comapped.Aux[V, Value, DL],
      reify: Reify.Aux[DL, DL],
      mapper: Mapper.Aux[createValuePoly.type, DL, V]
  ): A = gen.from(mapper(reify()))
}

val emptyInstance = empty[Instance]()
println(s"Empty valA: ${emptyInstance.valA.attrDef} - ${emptyInstance.valA.valueT}") 
//Empty valA: Definition(defA) - None
println(s"Empty valB: ${emptyInstance.valB.attrDef} - ${emptyInstance.valB.valueT}") 
//Empty valB: Definition(defB) - None
println(s"Empty valC: ${emptyInstance.valC.attrDef} - ${emptyInstance.valC.valueT}") 
//Empty valC: Definition(defC) - None
1 голос
/ 09 апреля 2020

То, что вы хотите сделать, может быть достигнуто намного проще без использования Typeable (и, кстати, Typeable[T] следует использовать как класс типов, а не как миксин). В общем, шаблон для получения классов типов с бесформенным выглядит следующим образом:

import shapeless._

trait Empty[A] {
  def createValue(): A
}
object Empty {
  // summoning method
  def apply[T](implicit empty: Empty[T]): T = empty.createValue()

  // implicits for HNil and HCons build HList if we provide the right implicits

  implicit val emptyHNil: Empty[HNil] = () => HNil

  implicit def emptyHCons[H, T](
    implicit 
    head: Empty[H],
    tail: Empty[T]
  ): Empty[H :: T] = () => head.createValue() :: tail.createValue()

  // Generic will translate it HList into Product type

  implicit def emptyProduct[T, Rep](
    implicit
    gen: Generic.Aux[T, Rep],
    empty: Empty[Rep]
  ): Empty[T] = () => gen.from(empty.createValue())

  // if you need default instances accessible everywhere without import put them here
}

используется как

case class Instance(int: Int, string: String)
implicit int: Empty[Int] = () => 0
implicit int: Empty[String] = () => ""
Empty[Instance].createValue[Instance] // Instance(0, "")

В вашем случае вам придется сделать доступным Value[T] s в неявной области видимости и получить Value[Instance] из меньших значений (как в примере Empty выше). Как вы предоставляете базовые c Value экземпляры в неявной области, зависит от вас: вы можете поместить их в сопутствующий объект, или определить вручную или получить из других типов, которые уже находятся в неявной области.

Long Короче говоря, то, что вы хотите, должно быть возможным, но вы должны сначала поместить вещи в неявную сферу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...