Calling generic function with Functor using subclass (cats/scalaz)

北战南征 提交于 2019-12-01 05:24:42

问题


I've been messing around with some basic examples of Cats/Scalaz and also walking through tutorials to get a feel and I've hit a case which I'm sure there is a solution to.

Is it possible to call a generalized function that takes a contextualized value (F[A]) with a Functor view (F[_] : Functor) with a context that is <: F? I'm aware that Functor is invariant on type F[_], and I'm also aware of the existence of Functor.widen, but it seems strange that there is no way to implicitly widen my type for use in a general function.

An example in Cats (a similar example with Scalaz exists as well):

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

Of course, summoning the Functor for Option explicitly and mapping works as expected

Functor[Option].map(Some(1))(_ + 1) // Some(2)

So my question is: Does the signature of the general function need to change to account for the subclassed context, is there some sort of "implicit widening" that I don't know about, or is this just an unfortunate drawback to functional programming in Scala using the stdlib?


回答1:


This isn't generally possible as Dmytro's answer points out. This is the reason that cats/scalaz exposes a .some extension method that is typed as returning Option, whereas using the Some constructor returns Some;

takesAFunctor(1.some)

Alternately you can use the more general Apply syntax; takesAFunctor(1.pure[Option])

is there some sort of "implicit widening" that I don't know about, or is this just an unfortunate drawback to functional programming in Scala using the stdlib?

The implicit widening you see when summoning the Option functor manually is covariance. The instance is defined invariantly for Option which is why Some isn't acceptable - the compiler can't find the implicit, but Functor[Option].map expects an Option or any subtype of Option, which is why Some works.

The drawback that you mention here is basically an impedance mismatch between the java-ish covariant subtyping and the more haskell-ish invariantly typed typeclasses




回答2:


// No implicit for Functor[Some]. 
// Makes sense, but can we summon one since we have a Functor[Option]?

How would you define such instance in general case?

  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)
  }

doesn't work since functor.map(ga)(f) is generally of type F[B] and not necessarily G[B].

So generally no, it's impossible to derive functor for subtype constructor and reasons are fundamental.

Functor F maps objects T to objects F[T] and morphisms f: A => B to morphisms map(f): F[A] => F[B] (plus some laws). F in F[B] is in covariant position and F in F[A] is in contravariant position, so the only option for functor type class is to be invariant in type constructor.

By the way, you can also call takesAFunctor as takesAFunctor[Option](Some(1)).



来源:https://stackoverflow.com/questions/47181109/calling-generic-function-with-functor-using-subclass-cats-scalaz

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!