我一直在弄乱一些 Cats/Scalaz 的基本示例,并且还浏览了教程以获得感觉,我遇到了一个案例,我相信有一个解决方案。
是否可以调用一个广义函数,该函数采用具有函子视图(F[_] : Functor
)的上下文化值(F[A]
),上下文<: F
?我知道 Functor 在类型F[_]
上是不变的,我也知道Functor.widen
的存在,但似乎很奇怪,没有办法隐式地扩大我的类型以用于一般函数。
Cats 中的一个例子(Scalaz 也存在类似的例子):
import cats.instances.all._
import cats.syntax.all._
def takesAFunctor[F[_] : cats.Functor](f: F[Int]) = f.map(_ + 1)
takesAFunctor(Option(1)) // Works fine (of course)
takesAFunctor(Some(1)) // No implicit for Functor[Some]. Makes sense, but can we summon one since we have a Functor[Option]?
takesAFunctor(Some(1): Option[Int]) // Works but very verbose
当然,显式调用 Option 的函子并映射按预期工作
Functor[Option].map(Some(1))(_ + 1) // Some(2)
所以我的问题是:通用函数的签名是否需要更改以考虑子类化上下文,是否存在某种我不知道的"隐式加宽",或者这只是使用 stdlib 在 Scala 中进行函数式编程的一个不幸缺点?
// No implicit for Functor[Some]. // Makes sense, but can we summon one since we have a Functor[Option]?
在一般情况下,您将如何定义此类实例?
implicit def subtypeFunctor[F[_], G[T] <: F[T]](implicit functor: Functor[F]): Functor[G] = new Functor[G] {
override def map[A, B](ga: G[A])(f: A => B): G[B] = functor.map(ga)(f)
}
不起作用,因为functor.map(ga)(f)
通常是F[B]
型,不一定是G[B]
.
所以通常不会,不可能为子类型构造函数派生函子,原因是根本的。
函子F
将对象T
映射到对象F[T]
,将态射f: A => B
映射到态射map(f): F[A] => F[B]
(加上一些定律)。F[B]
中的F
处于协变位置,F[A]
中的F
处于逆变位置,因此函子类型类的唯一选择是在类型构造函数中不变。
顺便说一句,您也可以将takesAFunctor
称为takesAFunctor[Option](Some(1))
.
正如Dmytro的回答所指出的那样,这通常是不可能的。这就是为什么 cats/scalaz 公开了一个类型为返回Option
的.some
扩展方法,而使用Some
构造函数返回Some
;
takesAFunctor(1.some)
或者,您可以使用更通用的Apply
语法;takesAFunctor(1.pure[Option])
是否有某种我不知道的"隐式加宽",或者这只是使用 stdlib 在 Scala 中进行函数式编程的一个不幸缺点?
手动召唤 Option 函子时看到的隐式加宽是协方差。该实例是为Option
始终定义的,这就是为什么Some
是不可接受的 - 编译器找不到隐式,但Functor[Option].map
期望Option
或任何子类型的Option
,这就是 有些工作的原因。
你在这里提到的缺点基本上是java-ish协变子类型和更haskell-ish的不变类型类之间的阻抗不匹配