Shapeless: перебирать типы в копродукте

Я хочу сделать что-то действительно простое, но я изо всех сил пытаюсь выработать правильный поиск или просто понять некоторые из решений, которые я видел.

Дан метод, который принимает параметр общего типа, который является Coproduct;

def apply[T <: Coproduct] = {
  ...
}

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

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

class ThingMaker[Entities <: Coproduct] private {
  def doThings(item: Entities): Set[Fact] = {
    ...
  }

def register[A <: Product with Serializable]: ThingMaker[A :+: Entities] = {
    // useful work can be done here on a per type basis
    new ThingMaker[A :+: Entities]
  }
}

object ThingMaker {
  def register[A <: Product with Serializable]: ThingMaker[A :+: CNil] = {
    // useful work can be done here on a per type basis
    new ThingMaker[A :+: CNil]
  }
}

1 ответ

Решение

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

def apply[T <: Coproduct](co: T): Any = co match {
  case Inl(MyCaseClass(a, b, c)) => ???
  ...
}

... но если вы хотите быть более точным, например, иметь возвращаемый тип, который зависит от входных данных, или проверять типы внутри этого совместного продукта для вызова имплицитов, то вы можете написать точно такое же выражение сопоставления с образцом, используя класс типов и несколько неявных определений:

trait MyFunction[T <: Coproduct] {
  type Out
  def apply(co: T): Out
}

object MyFunction {
  // case Inl(MyCaseClass(a, b, c)) =>
  implicit val case1 = new MyFunction[Inl[MyCaseClass]] {
    type Out = Nothing
    def apply(co: Inl[MyCaseClass]): Out = ???
  }

  // ...
}

В общем, когда вы хотите выполнить итерации по всем типам копроизведения, вы всегда будете следовать одной и той же хвостовой рекурсивной структуре. Как функция:

def iterate[T <: Coproduct](co: T): Any = co match {
  case Inr(head: Any)       => println(v)
  case Inl(tail: Coproduct) => iterate(tail)
  case CNil                 => ???
}

Или как "функция с зависимой типизацией":

trait Iterate[T <: Coproduct]
object Iterate {
  implicit def caseCNil = new Iterate[CNil] {...}
  implicit def caseCCons[H, T <: Coproduct](implicit rec: Iterate[T]) =
    new Iterate[H :+: T] {...}
}

Например, вы можете получить имя каждого типа в копродукции, используя дополнение ClassTag неявный:

trait Iterate[T <: Coproduct] { def types: List[String] }

object Iterate {
  implicit def caseCNil = new Iterate[CNil] {
    def types: List[String] = Nil
  }

  implicit def caseCCons[H, T <: Coproduct]
    (implicit
      rec: Iterate[T],
      ct: reflect.ClassTag[H]
    ) =
      new Iterate[H :+: T] {
        def types: List[String] = ct.runtimeClass.getName :: rec.types
      }
}

implicitly[Iterate[Int :+: String :+: CNil]].types // List(int, java.lang.String)

Благодаря тому, что Scala позволяет вам влиять на неявный приоритет, на самом деле можно преобразовать любую рекурсивную функцию с сопоставлением с шаблоном в этот шаблон "зависимо типизированной функции". Это не похоже на Haskell, где такая функция может быть написана только в том случае, если случаи вызова выражения соответствия доказуемо не перекрываются.

Другие вопросы по тегам