仅为 class 标记的参数实现仿函数映射

Implementing functor map for class-tagged arguments only

我有以下数据结构:

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 在 Scala 类型 的类别中描述了一个 内函子 - 也就是说,您应该能够 map 使用函数 A => B 其中 AB 必须支持任何 Scala 类型。

您拥有的是一个数学函子,但属于具有 ClassTag 的不同的较小类型类别。这些通用仿函数有点不常见——我认为对于 stdlib 类型,只有 SortedSet 可以作为一类有序事物的仿函数——所以它现在在 Scala FP 中是相当未开发的领域,只是在 Scalaz 8 中有所传言。

Cats 没有任何工具来抽象这些东西,因此您不会获得任何实用方法和生态系统支持。如果您想自己滚动,可以使用

科约内达

Coyoneda 可以从任何类型构造函数 F[_] 对 Scala 类型创建一个内函子。思路很简单:

  • 有一些初始值F[Initial]
  • 有函数Initial => A
  • mapA => B,你不触及初始值,只是简单地组合函数得到Initial => B

您可以将任何 F[A] 提升到 cats.free.Coyoneda[F, A]。问题是如何把F[A]弄出来。

如果F是一个cats.Functor,那么你可以使用它的原生map是完全自然的,事实上,结果与根据函子定律 (x.map(f).map(g) <-> x.map(f andThen g)).

,使用 Coyoneda 并直接使用 F

您的情况并非如此。但是你可以撕开 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

一个明显的限制是你需要在任何你想调用 unCo 的地方有一个 ClassTag[A] - 即使你不需要它来 create 首先是 MyDaSt[A] 的实例。

不太明显的一点是,您不会自动获得没有行为差异的保证。是否可以取决于您的 map 做什么 - 例如如果它只是分配一些 Arrays,它应该不会引起问题。