如何让编译器自动识别父类GenericLookup的Applicative实例以支持cats语法调用?
这个问题我之前也碰到过,本质是Scala的类型推断会优先绑定到最具体的类型(也就是你的Key类),而cats的tupled、mapN这些语法糖会去寻找对应具体类型的Applicative实例——但你只给父类GenericLookup写了实例,所以编译器就懵了,找不到Applicative[Key]就报错了。
给你两个实用的解决办法,推荐第一个,简单又安全:
方法一:添加隐式转换自动提升Key到GenericLookup
你只需要在GenericLookup的伴生对象里加一个隐式转换函数,让编译器在需要的时候自动把Key实例转成GenericLookup类型:
object GenericLookup { implicit val applicative: Applicative[GenericLookup] = /* 你的原有实现 */ // 新增这个隐式转换,让Key自动转成GenericLookup implicit def keyToGenericLookup[A](key: Key[A]): GenericLookup[A] = key }
加完之后,你直接写(Foo, Bar, Age).tupled就行——编译器会自动把每个Key转换成GenericLookup,此时元组的类型变成(GenericLookup[String], GenericLookup[Boolean], GenericLookup[Int]),自然就会找到你定义的Applicative[GenericLookup]实例,顺利调用cats的语法方法。
这个方法相当于把你手动调用.generic的过程自动化了,完全不需要额外的代码修改调用逻辑。
方法二:为Key实现代理式Applicative实例
如果你不想用隐式转换,也可以给Key自己写一个Applicative实例,把所有操作转发给GenericLookup的实例。不过这个方法有个坑:因为GenericLookup的ap方法返回的是GenericLookup[B],不一定是Key[B],所以强制转换会有运行时风险,而且pure方法也需要你自己实现(毕竟Key是靠keyword构造的,纯值怎么映射成Key需要你自己定义逻辑)。示例代码大概是这样:
object Key { implicit val applicative: Applicative[Key] = new Applicative[Key] { // 这里需要你根据业务逻辑实现pure,比如定义一个特殊的keyword或者其他规则 override def pure[A](x: A): Key[A] = ??? override def ap[A, B](ff: Key[A => B])(fa: Key[A]): Key[B] = // 先转成GenericLookup调用ap,再强制转成Key,有运行时风险 GenericLookup.applicative.ap(ff: GenericLookup[A => B])(fa: GenericLookup[A]).asInstanceOf[Key[B]] } }
这种方法局限性比较大,除非你能保证GenericLookup的所有操作返回的实例都是Key,否则不推荐用。
总的来说,第一个方法是最省心的,完全不需要改变你的调用习惯,也没有额外风险,直接解决问题。
备注:内容来源于stack exchange,提问作者Dylan

