Как использовать shapeless для преобразования универсального Map[String, Any] в класс case внутри универсальной функции?

Я пытаюсь найти ответ здесь https://stackoverflow.com/a/31641779/1586965

То есть я хочу иметь возможность конвертировать (потенциально вложенный) Map[String, Any] в класс case.

scalaVersion := "2.11.8"
val shapelessV = "2.3.3"

Если я попытаюсь обернуть код в приведенном выше ответе в другую общую функцию, я не смогу его скомпилировать.

import shapeless._, labelled._
import FromMap._

def usesGenerics[P](map: Map[String, Any]): P = {
  to[P].from(mp).get
}

Я получаю следующую ошибку компиляции

could not find implicit value for parameter gen: shapeless.LabelledGeneric.Aux[P,R]

ОБНОВИТЬ

Этот подход намного проще и полнее (обрабатывает больше крайних случаев): https://stackoverflow.com/a/55355685/1586965

ПОЛНЫЙ ОТВЕТ

Это включает в себя как первоначальную идею Трэвиса, так и исправление Дмитрия, и, наконец, некоторое упрощение.

import shapeless._, labelled.{FieldType, field}

trait FromMap[L <: HList] {
  def apply(m: Map[String, Any]): Option[L]
}

trait LowPriorityFromMap {
  implicit def hconsFromMap1[K <: Symbol, V, T <: HList](implicit
                                                         witness: Witness.Aux[K],
                                                         typeable: Typeable[V],
                                                         fromMapT: Lazy[FromMap[T]]
                                                        ): FromMap[FieldType[K, V] :: T] = new FromMap[FieldType[K, V] :: T] {
    def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
      v <- m.get(witness.value.name)
      h <- typeable.cast(v)
      t <- fromMapT.value(m)
    } yield field[K](h) :: t
  }
}

object FromMap extends LowPriorityFromMap {
  implicit val hnilFromMap: FromMap[HNil] = new FromMap[HNil] {
    def apply(m: Map[String, Any]): Option[HNil] = Some(HNil)
  }

  implicit def hconsFromMap0[K <: Symbol, V, R <: HList, T <: HList](implicit
                                                                     witness: Witness.Aux[K],
                                                                     gen: LabelledGeneric.Aux[V, R],
                                                                     fromMapH: FromMap[R],
                                                                     fromMapT: FromMap[T]
                                                                    ): FromMap[FieldType[K, V] :: T] =
    new FromMap[FieldType[K, V] :: T] {
      def apply(m: Map[String, Any]): Option[FieldType[K, V] :: T] = for {
        v <- m.get(witness.value.name)
        r <- Typeable[Map[String, Any]].cast(v)
        h <- fromMapH(r)
        t <- fromMapT(m)
      } yield field[K](gen.from(h)) :: t
    }
}

trait CaseClassFromMap[P <: Product] {
  def apply(m: Map[String, Any]): Option[P]
}

object CaseClassFromMap {
  implicit def mk[P <: Product, R <: HList](implicit gen: LabelledGeneric.Aux[P, R],
                                            fromMap: FromMap[R]): CaseClassFromMap[P] = new CaseClassFromMap[P] {
    def apply(m: Map[String, Any]): Option[P] = fromMap(m).map(gen.from)
  }

  def apply[P <: Product](map: Map[String, Any])(implicit fromMap: CaseClassFromMap[P]): P = fromMap(map).get
}

person samthebest    schedule 04.03.2019    source источник
comment
Что такое FromMap? можешь показать определение?   -  person Yuval Itzchakov    schedule 04.03.2019
comment
Я понятия не имею, в этом ли проблема, но вы можете обновить до 2.11.12? 2.11.8 три года — это как древняя история.   -  person Travis Brown    schedule 04.03.2019
comment
(Насколько мне известно, любая версия Spark, которая работает на 2.11.8, должна работать и на 2.11.12.)   -  person Travis Brown    schedule 04.03.2019
comment
У @TravisBrown Spark также есть порт 2.12, возможно, было бы неплохо попробовать и 2.12.   -  person Yuval Itzchakov    schedule 04.03.2019
comment
Должно быть, у вас происходит что-то странное, потому что у меня это прекрасно работает с вашими версиями, и Symbol#name возвращать NameType не имеет смысла. Вам нужно будет предоставить больше контекста о вашем импорте и т. д.   -  person Travis Brown    schedule 04.03.2019
comment
Спасибо @TravisBrown, да, мой импорт все испортил, у меня было import scala.reflect.runtime.universe._   -  person samthebest    schedule 04.03.2019
comment
@TravisBrown Я хочу вызвать to внутри другой универсальной функции, но она не может найти неявное значение, необходимое для from, особенно потому, что я не знаю, что такое R. Я обновил вопрос кодом, который пробовал, не могли бы вы взглянуть?   -  person samthebest    schedule 04.03.2019
comment
@samthebest Не могли бы вы включить пример использования для этого? Я пытаюсь использовать ваш код следующим образом: CaseClassFromMap[P](params), где params равно Map[String, Any], но получаю сообщение об ошибке: Could not find implicit value for parameter fromMap: ...CaseClassFromMap[P]. Я где-то пропустил импорт?   -  person Alan Thomas    schedule 03.01.2020
comment
@AlanThomas Попробуйте это вместо этого, гораздо проще следовать: stackoverflow.com/a/55355685/1586965   -  person samthebest    schedule 04.01.2020
comment
@samthebest хм, отличное решение! Я попробую. Меня беспокоит только то, доказуемы ли формы во время компиляции. Спасибо!   -  person Alan Thomas    schedule 05.01.2020


Ответы (1)


Это работает для вас?

def usesGenerics[P, R <: HList](map: Map[String, Any])(implicit gen: LabelledGeneric.Aux[P, R], fromMap: FromMap[R]): P = {
  to[P].from[R](map).get
}

Вам нужен еще один класс типов

  trait CaseClassFromMap[P <: Product] {
    def apply(m: Map[String, Any]): Option[P]
  }
  object CaseClassFromMap {
    implicit def mk[P <: Product, R <: HList](implicit
      gen: LabelledGeneric.Aux[P, R],
      fromMap: FromMap[R]
      ): CaseClassFromMap[P] = new CaseClassFromMap[P] {
      def apply(m: Map[String, Any]): Option[P] = to[P].from[R](m)
    }
  }

  def usesGenerics[P <: Product](map: Map[String, Any])(implicit fromMap: CaseClassFromMap[P]): P = {
    fromMap(map).get
  }

  usesGenerics[Person](mp)
person Dmytro Mitin    schedule 04.03.2019
comment
Как мне это назвать, так как теперь мне нужны два параметра типа. Первый я знаю, что это Person, но что поставить на второй val converted2 = usesGenerics[Person, ???](mp) - person samthebest; 05.03.2019
comment
Да я вижу. Тогда вам нужен еще один класс типов. Я обновил свой ответ. - person Dmytro Mitin; 05.03.2019