2017-07-25 76 views
2

有了这个类型的类用于将映射到一个案例类创建一个泛型类的函数被用作toCaseClass[User](aUserMap) // returns UserHOWTO使用仿函数

但我也希望能够通过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的答案。 所以解决这个问题的'技巧'是在Functor类型之前的类中首先捕获'T'类型。我喜欢@'Jasper-m解决方案与'apply'方法,因为这会保持语法几乎与以前相似。
虽然我做了一些调整。由于已经有'ToCaseClassMapper'类,它也捕获'T'类型,我决定将它与'ToCaseClass'类结合起来。此外,使用@ Jasper-m的方法,当使用'toCaseClass'函数映射某个值时,如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) 
    } 
} 

由于使用了toCaseClass功能,其中ToCaseClassMapper实例已经隐含可用,我决定扔掉功能,只是与mapper.toCaseClass(_)取代它。这清理了一些不需要的代码,现在使用映射器的语法是相同的,无论该值是Map还是Option,List,Future(或任何其他Functor)。

回答

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]]的子类型,所以编译器做了一些奇怪的事情,推断FAny。解决方法是将类型参数A而不是Any,或使用存在类型Map[String,_]

+0

您使用哪个版本的scala来检查它?我的2.11.8给出:'找不到所有类型cat的证据参数的隐式值。函数[Any]' – dk14

+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 - 它会缩短语法