Преобразование класса case в другой путем развертывания типов в Scala 3

У меня есть перечисление, представляющее контейнер и два класса case:

      enum Container[+A]:
  case Value(value: A)
  case Default(default: A)

  def get: A = this match
    case Value(value)     => value
    case Default(default) => default

case class PersonTemplate(name: Container[String], age: Container[Int], enabled: Container[Boolean])
case class Person(name: String, age: Int, enabled: Boolean)

и я хочу написать общую функцию в Scala 3, которая преобразует все классы case, например PersonTemplateв их аналог, как Person, что-то типа:

      def extract[I <: Product, O <: Product](input: I): O = ???

val initial = PersonTemplate(Value("John"), Default(12), Default(true))
val final = extract[PersonTemplate, Person](initial)
// Result: Person("John", 12, true)

Я попробовал несколько подходов, но ни один из них не увенчался успехом, в основном потому, что я не понимаю, как использовать Scala 3, который для меня выглядит иначе, чем Scala 2 Shapeless. HList(и даже в бесформенном мне не так хорошо).

Мой общий подход был таким:

  1. Преобразуйте класс case в Tuple. Для этого я нашел Tuple.fromProductTyped
  2. Ограничьте каждый элемент, чтобы он был Container[_]. я нашел Tuple.IsMappedByчтобы гарантировать, что кортеж имеет правильную форму и Tuple.InverseMapэто, кажется, извлекает тип внутри контейнера. Однако я не уверен, куда поместить этот код.
  3. Применить (поли?) функцию к каждому значению, которое вызывает Container.get. Из того немногого, что я нашел в сети, я использовал много .asInstanceOfи мне это показалось неправильным.
  4. Преобразовать полученный Tupleк типу вывода с помощью summon[Mirror.Of[O]].fromProduct(output)

Для полноты картины это последний код, который я пробовал, но он, конечно же, не работает:

      def resolve[I <: Product: Mirror.ProductOf, O: Mirror.ProductOf](input: I): O =
  val processed =
    Tuple
      .fromProductTyped(input)
      .map { [T] => (value: T) => ??? }

  summon[Mirror.Of[O]].fromProduct(processed)

type ExtractG = [G] =>> G match {
  case Container[a] => a
}

def process[I <: Tuple, O <: Tuple](input: I)(using Tuple.IsMappedBy[Container][I]): O =
  input.map { [A] => (a: A) =>
    a.asInstanceOf[Container[_]].get.asInstanceOf[ExtractG[A]]
  }.asInstanceOf[O]

1 ответ

Ну, если вы не возражаете против небольшого кастинга, вы можете сделать это:

      def unwrapper[From <: Product, To](
  using To: Mirror.ProductOf[To],
  From: Mirror.ProductOf[From],
  ev: From.MirroredElemTypes =:= Tuple.Map[To.MirroredElemTypes, Container]
): (From => To) =
  from => To.fromProduct {
    from.productIterator
        .toArray
        .map(_.asInstanceOf[Container[_]].get)
        .foldRight[Tuple](EmptyTuple)(_ *: _)
  }

@main def run =
  import Container._
  val unTemplate = unwrapper[PersonTemplate, Person]
  println(unTemplate(PersonTemplate(Value("foo"), Default(42), Default(false))))

Запрошено Fromа также evслужат только для подтверждения типовой безопасности всего литья. IMO, зеркальному механизму не хватает способности работать с вещами безопасным способом без макросов, таких как бесформенная банка.

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