我具有以下数据结构:
class MyDaSt[A]{
def map[B: ClassTag](f: A => B) = //...
}
我想实现一个Functor
实例,以便能够使用即席多态性。显而易见的尝试如下:
implicit val mydastFunctor: Functor[MyDaSt] = new Functor[MyDaSt] {
override def map[A, B](fa: MyDaSt[A])(f: A => B): MyDaSt[B] = fa.map(f) //compile error
}
它显然不能编译,因为我们没有提供隐式的ClassTag[B]
。但是可能仅将map
与功能f: A => B
一起使用,以致存在ClassTag[B]
。否则编译错误。我的意思是这样的:
def someFun[A, B, C[_]: Functor](cc: C[A], f: A => B) = cc.map(f)
val f: Int => Int = //...
val v: MyDaSt[Int] = //...
someFunc(v, f) //fine, ClassTag[Int] exists and in scope
我无论如何都不能更改其实现,但是我可以创建包装器(看起来没有帮助)或继承。我可以自由使用任何版本的shapeless
。
我目前认为在这种情况下不变形是一种解决方法...
cats.Functor
描述了
map
来A => B
,其中A
和B
必须支持任何Scala类型。 您拥有的是一个数学函子,但具有ClassTag
的类型是另一种较小的类型。这些通用仿函数并不常见-我认为对于stdlib类型,只有SortedSet
可以用作一类有序事物的仿函数-因此,目前在Scala FP中这是一个尚未开发的领域,仅在Scalaz 8中有传言。
Coyoneda
Coyoneda
可以使任何类型构造函数F[_]
的Scala类型成为endofunctor。这个想法很简单:
具有一些初始值F[Initial]
Initial => A
map
和A => B
,您无需触摸初始值,而只需编写函数即可获得Initial => B
F[A]
提升到cats.free.Coyoneda[F, A]
。问题是如何将F[A]
取出。F
是cats.Functor
,那么可以使用它的本机map
是很自然的,实际上,使用Coyoneda
和F
的结果不会有任何区别。直接根据函子定律(x.map(f).map(g) <-> x.map(f andThen g)
)。就您而言,不是。但是您可以将cats.free.Coyoneda
拆开,然后委派给自己的map
:
def coy[A](fa: MyDaSt[A]): Coyoneda[MyDaSt, A] = Coyoneda.lift(fa)
def unCoy[A: ClassTag](fa: Coyoneda[MyDaSt, A]): MyDaSt[A] =
fa.fi.map(fa.k) // fi is initial value, k is the composed function
将允许您使用期望cats.Functor
的功能:
def generic[F[_]: Functor, A: Show](fa: F[A]): F[String] = fa.map(_.show)
unCoy(generic(coy(v))) // ok, though cumbersome and needs -Ypartial-unification on scala prior to 2.13
(可运行示例on scastie)
一个明显的局限性是,您需要在要调用ClassTag[A]
的任何位置都具有一个unCo
-即使您首先不需要使用它[MyDaSt[A]
的实例。不太明显的是,您不会自动保证没有行为差异。是否可以,取决于您的
map
所做的事情-例如如果只是分配一些Array
,就不会引起问题。