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

时间:2017-07-25 08:34:07

标签: scala functor shapeless scala-cats

使用此类型类将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)。

2 个答案:

答案 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,_]