使用此类型类将Map转换为案例类:
/**
* Type class for transforming a map of values into a case class instance.
*
* @tparam T Wanted case class type.
*/
@implicitNotFound("Missing ToCaseClassMapper implementation for type ${T}")
trait ToCaseClassMapper[T <: Product] {
def toCaseClass(map: Map[String, Any]): T
}
此函数隐式获取正确的映射器
def toCaseClass[T <: Product](map: Map[String, Any])(implicit mapper: ToCaseClassMapper[T]): T = {
mapper.toCaseClass(map)
}
可以用作toCaseClass[User](aUserMap) // returns User
但我也希望能够在Option [Map []]或Future [Map []]或List [Map []]中使用此功能。 所以我使用这样的仿函数实现了一个泛型函数:
def toCaseClass[T <: Product, F[_]: cats.Functor](fOfMaps: F[Map[String, Any]])(implicit mapper: ToCaseClassMapper[T]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => toCaseClass(map)(mapper))
}
但是现在这个函数必须用作toCaseClass[User,List](listOfUserMaps) // returns List[User]
。
但是,我希望能够将此功能用作
toCaseClass[User](listOfMaps)
toCaseClass[User](futureOfMap)
toCaseClass[User](optionOfMap)
无需指定仿函数类型。
这有点可能吗?
可以用无形的懒惰来解决这个问题吗?
编辑:解决方案
感谢@Jasper-m和@ dk14的回答。
所以&#39;诀窍&#39;解决这个问题就是捕捉类型&#39; T&#39;首先在Functor类型之前的类中。通过&#39; apply&#39;我喜欢@Jasper-m解决方案方法,因为这将保持语法几乎与以前相似
我做了一些调整。因为已经有了ToCaseClassMapper&#39;我也决定将它与“ToCaseClass”类型结合起来。类。此外,使用@ Jasper-m的方法,当使用&#39; toCaseClass&#39;在映射某些值(例如Option(value).map(toCaseClass)
时,函数的函数在值为Map或List [Map]时,toCaseClass
的用法必须不同。
我的解决方案如下:
@implicitNotFound("Missing ToCaseClassMapper implementation for type ${T}")
trait ToCaseClassMapper[T <: Product] {
def toCaseClass(map: Map[String, Any]): T
import scala.language.higherKinds
def toCaseClass[F[_]: cats.Functor, A](fOfMaps: F[Map[String, A]]): F[T] = {
cats.Functor[F].map(fOfMaps)(toCaseClass)
}
}
由于ToCaseClassMapper
实例已经在使用toCaseClass
函数的地方隐式可用,我决定抛弃该函数,只需将其替换为mapper.toCaseClass(_)
。这清理了一些不需要的代码,现在使用映射器的语法是相同的,无论值是Map还是Option,List,Future(或任何其他Functor)。
答案 0 :(得分:1)
这有效:
class Mapper[T <: Product](implicit val mapper: ToCaseClassMapper[T]){
def toCaseClass[F[_]: cats.Functor, Z <: Map[String, Any]](fOfMaps: F[Z]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => mapper.toCaseClass(map))
}
}
object Mapper{
def apply[T <: Product: ToCaseClassMapper] = new Mapper[T]{}
}
import cats.implicits._
Mapper[User].toCaseClass(List(Map("aaa" -> 0)))
除了明显的介绍类(分裂类型参数)之外,还使用了很少的技巧:
1)将mapper
移动到构造函数,以便首先解析它(不确定它是否有帮助)
2)肯定有用的是引入Z <: Map[String, Any]
,否则scala(至少我的旧版本2.11.8)会因为某种原因将F[_]
推断为Any
P.S。您也可以使用apply
代替toCaseClass
- 它会缩短语法
答案 1 :(得分:1)
目前,在Scala中不可能显式提供一个类型参数,并且推断出同一类型参数列表中的另一个类型参数,目前也不可能为方法提供多个类型参数列表。解决方法是创建一个帮助器类,并分两个阶段拆分方法调用:首先创建一个辅助类的实例,然后在该对象上调用apply
方法。
class ToCaseClass[T <: Product] {
def apply[F[_]: cats.Functor, A](fOfMaps: F[Map[String, A]])(implicit mapper: ToCaseClassMapper[T]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => toCaseClass(map)(mapper))
}
}
def toCaseClass[T <: Product] = new ToCaseClass[T]
def toCaseClass[T <: Product](map: Map[String, Any])(implicit mapper: ToCaseClassMapper[T]): T = {
mapper.toCaseClass(map)
}
toCaseClass[User](listOfMaps)
toCaseClass[User](futureOfMap)
toCaseClass[User](optionOfMap)
编辑:正如dk14所指出的,这里仍然存在类型推断问题,其中 F
被推断为Any
。我不知道是什么原因引起的,但我认为这是与这种模式解决的问题分开的正交问题。
编辑2:我想通了。这是因为F
在其类型参数中是不变的。 F[Map[String, String]]
不是F[Map[String, Any]]
的子类型,因此编译器会执行一些奇怪的操作并将F
推断为Any
。解决方案是放置类型参数A
而不是Any
,或使用存在类型Map[String,_]
。