如何使用仿函数创建泛型 class 函数

Howto create a generic type class function using functors

具有此类型 class 用于将 Map 转换为 case class:

/**
* 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)

无需指定函子类型。 这有可能吗?
Shapeless 的 Lazy 可以用来解决这个问题吗?

编辑:解决方案
感谢@Jasper-m 和@dk14 的回答。 所以解决这个问题的'trick'就是在Functor类型之前的class中先捕获类型'T'。我喜欢使用 'apply' 方法的 @Jasper-m 解决方案,因为这将使语法与以前的语法几乎相似。
不过我做了一些调整。因为已经有 'ToCaseClassMapper' class 也捕获类型 'T',所以我决定将它与 'ToCaseClass' class 结合起来。此外,使用@Jasper-m 的方法,在映射某些值(如 Option(value).map(toCaseClass) 时使用 'toCaseClass' 函数时,当值是 Map 或 a 时,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),使用映射器的语法都是相同的。

这个有效:

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)))

除了明显引入 class(拆分类型参数)外,还使用了一些技巧:

1) 将 mapper 移动到构造函数以便首先解决它(不确定是否有帮助)

2) 绝对有帮助的是引入 Z <: Map[String, Any],否则 scala(至少我的旧版本 2.11.8)会出于某种原因将 F[_] 推断为 Any

P.S。您也可以使用 apply 而不是 toCaseClass - 它会缩短语法

目前在 Scala 中无法显式提供一个类型参数并推断同一类型参数列表中的另一个类型参数,目前也不可能为一个方法拥有多个类型参数列表。解决方法是创建一个助手 class 并将方法调用分为两个阶段:首先创建助手的实例 class,然后对该对象调用 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,_].