首页 > 解决方案 > 仅为类标记参数实现函子映射

问题描述

我有以下数据结构:

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任何版本。

我目前认为在这种情况下无形是一种方法......

标签: scalafunctional-programmingfunctorshapelessscala-cats

解决方案


我将扩展评论所触及的内容:

函子

cats.Functor描述了Scala 类型类别中的 endofunctor - 也就是说,您应该能够使用并且必须支持任何 Scala 类型map的函数。A => BAB

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

Cats 没有任何工具可以对这些东西进行抽象,因此您不会获得任何实用方法和生态系统支持。如果您想自己动手,可以使用@DmytroMitin 链接的答案

科米达

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

  • 有一些初始值F[Initial]
  • 有一个功能Initial => A
  • mapwith A => B,你不会触及初始值,而只是简单地组合函数来获得Initial => B

您可以将任何内容提升F[A]cats.free.Coyoneda[F, A]. 问题是如何F[A]出去。

如果F是 a cats.Functor,那么你可以使用它是完全自然的 native map,事实上,由于函子定律() ,使用Coyoneda和直接使用不会有任何结果差异。Fx.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

scastie 上的可运​​行示例)

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

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


推荐阅读