Calling generic function with Functor using subcla

2019-05-06 20:49发布

问题:

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