【问题标题】:Implementing functor map for class-tagged arguments only仅为类标记参数实现函子映射
【发布时间】:2019-12-05 09:58:08
【问题描述】:

我有以下数据结构:

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

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

【问题讨论】:

  • 简短的回答是你不能。首先,它会是一个有缺陷的Functor,因为它不支持任何A => B 而是ClassTag[B] -> A => B (它是前一个的子集)。其次,如果 Functorcats 你不能改变签名。最后,在您的通用示例中,没有办法将 ClassTag 传递给调用。
  • @LuisMiguelMejíaSuárez 所以在数据结构中使用这样的签名声明 map 会抑制函子语义......
  • AFAIK 是的,至少是完全合法的。您可以创建自己的TaggedFunctor,其map 始终需要ClassTag,然后您提供implicit def fromCatsFunctorToTaggedFunctor[F[_]](implicit functor: cats.Functor): TaggedFunctor[F] = ...,您需要使用隐式优先级,您将失去所有cats 提供,但您将重新获得编写通用函数的能力。 - PS:如果有更好的方法,值得在猫 gitter 房间询问。
  • @SomeName 你总是可以用def map[B: ClassTag](f: A => B) 创建MyDaSt。问题是为什么你需要它成为Functor。如果它是Functor,你会如何使用它?你能添加一些代码吗?有时你可以使用cats.free.Coyoneda

标签: scala functional-programming functor shapeless scala-cats


【解决方案1】:

我将详细介绍 cmets 所触及的内容:

函子

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

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

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

Coyoneda

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

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

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

如果Fcats.Functor,那么你可以使用它的原生map是完全自然的,而且实际上使用Coyoneda和使用@987654344在结果上不会有任何区别@ 直接,由于函子定律 (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

一个明显的限制是,您需要在要调用unCo 的任何位置有一个ClassTag[A] - 即使您不需要它来创建 MyDaSt[A] 的实例第一名。

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

【讨论】:

    猜你喜欢
    • 2012-01-04
    • 1970-01-01
    • 2012-01-26
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    • 2011-11-16
    • 2010-10-09
    • 2021-11-21
    相关资源
    最近更新 更多