Преобразование класса 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
(и даже в бесформенном мне не так хорошо).
Мой общий подход был таким:
- Преобразуйте класс case в Tuple. Для этого я нашел
Tuple.fromProductTyped
- Ограничьте каждый элемент, чтобы он был
Container[_]
. я нашелTuple.IsMappedBy
чтобы гарантировать, что кортеж имеет правильную форму иTuple.InverseMap
это, кажется, извлекает тип внутри контейнера. Однако я не уверен, куда поместить этот код. - Применить (поли?) функцию к каждому значению, которое вызывает
Container.get
. Из того немногого, что я нашел в сети, я использовал много.asInstanceOf
и мне это показалось неправильным. - Преобразовать полученный
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, зеркальному механизму не хватает способности работать с вещами безопасным способом без макросов, таких как бесформенная банка.