当前位置: 首页 > 知识库问答 >
问题:

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

邢璞
2023-03-14

使用此类型类将地图转换为case类:

/**
* 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)//返回用户

但我也希望能够将此函数与 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) // 返回 List[User]

但是,我希望能够将该功能用作

toCaseClass[User](listOfMaps)
toCaseClass[User](futureOfMap)
toCaseClass[User](optionOfMap)

无需指定函子类型。这在某种程度上是可能的吗?
Shapeless的懒惰可以用来解决这个问题吗?

编辑:解答< br >感谢@Jasper-m和@dk14的回答。因此,解决这个问题的“技巧”是在类中的仿函数类型之前先捕获类型“T”。我喜欢带有“apply”方法的@Jasper-m解决方案,因为这样可以保持语法和以前几乎一样。< br >不过,我做了一些调整。因为已经有一个“ToCaseClassMapper”类也捕获了类型“T ”,所以我决定将它与“ToCaseClass”类结合起来。此外,使用@Jasper-m的方法,当使用“toCaseClass”函数映射某些值时,如< code >选项(value)。map(toCaseClass)当值为map或List[Map]时,< code>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.tocaseCaseClass(_)替换它。这清理了一些不需要的代码,现在使用映射器的语法是相同的,无论值是Map还是Option、List、Future(或任何其他函子)。


共有2个答案

锺离飞尘
2023-03-14
匿名用户

这是可行的:

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)将映射器移动到构造函数,以便可以首先解决(不确定是否有帮助)

2) 引入<code>Z绝对有帮助

另外,你也可以使用< code>apply而不是< code>toCaseClass,这样可以缩短语法

唐兴贤
2023-03-14

目前,在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推断为。一种解决方案是将类型参数A而不是Any,或者使用存在类型映射[String,_]

 类似资料:
  • 问题内容: 我想创建一个KeyValue类,但以通用方式,这就是我写的内容: 错误显示:“令牌“>上的语法错误”,此令牌后应有标识符” 那我该如何在Java中创建一个通用构造函数呢? 问题答案: 您需要从构造函数的签名中删除:它已经隐式存在。

  • 问题内容: 如何创建泛型类型的数组?通用方法如何工作?它返回通用数组的副本。因此可以创建通用数组。但是如何?怎么能写一个类似的方法呢? 问题答案: 如果需要在运行时创建它,则至少需要在此时知道类型,因此可以使用以下方法: where 是泛型类型,是的类,并且是初始大小。 这里的文件

  • 我在我的一个实用程序类中有一个方法,它接受一个集合和一个类对象,并返回一个Iterable实例,该实例可以遍历作为指定类实例的集合的所有成员。其签名为: 这对于大多数用例都非常有效,但现在我需要将其与泛型类

  • 我试图实现一个接受泛型参数的函数定义,只要它扩展了另一个特定的泛型类型。简言之参数A必须扩展参数B,其中A和B都是泛型的。 下面是我的示例 用法示例如下 一些封闭的班级 和函数调用 我不想在抽象类声明中定义E,因为T已经在那里定义了。 我也试着做了以下几点: 将myList定义为接受扩展T的键 将E定义为T类型(无法找到如何指定它在函数中扩展T 但它从来都不起作用。有没有办法做到这一点?我在Sta

  • 问题内容: 如果我有一个像这样的抽象类: 还有一些从Item派生的类是这样的: 我不明白为什么我不能使用泛型调用构造函数: 我知道可以有一个没有构造函数的类型,但是这种情况是不可能的,因为Pencil具有没有参数的构造函数,而Item是抽象的。但是我从eclipse中得到了这个错误: 无法实例化 我不明白为什么的 T类型 ,以及如何避免这种情况? 问题答案: 无法使用Java类型系统来强制类层次结

  • 我试图创建自己的类型,在这些类型上我可以使用调用函数。 我如何创建自己的类型并像普通类型一样使用它们?我知道接口也不工作,但这是我的第一个猜测。