How to add type constraint to functors map function


#1

I’m unable to define a Functor for my abstract class Foo[V : Monoid] that have a type constraint. this is because of the map method of Functor which takes a B parameters which is not a Monoid.

My question is how and where can I add such constraint ?

Here’s a sample from what I’m trying to do :

import cats.Functor
import cats.kernel.Monoid

abstract class Foo[A : Monoid] {
  val a: A
}

object Foo {
  implicit def FooFunctor = new Functor[Foo] {
    override def map[A, B](fa: Foo[A])(f: (A) => B) = new Foo[B] {
      override val a: B = f(fa.a)
    }
  }
}

this raise the following exception :

Error:(12, 59) could not find implicit value for evidence parameter of type cats.kernel.Monoid[B]
    override def map[A, B](fa: Foo[A])(f: (A) => B) = new Foo[B] {

My first solution was adding the constraint in the map definition (override def map[A, B : Monoid]) but this is not legal because this will change the definition of the function in Functor and will raise the Exception :

Error:(12, 18) method map overrides nothing.

Can anyone please help me get out of this ? any suggestion would be appreciated.


#2

You’ve found just the direct problem,

this is not legal because this will change the definition of the function

but the real lesson is stated the other way: you cannot write a suitable Functor for Foo, because Foo doesn’t meet the minimum requirements. Speaking as informally as possible, those are “you can map A to anything you like, because it’s completely irrelevant to the structure of Foo.”

You can look for constrained functors, but IMO this approach doesn’t give us enough in generality to make up for what we lose in reason-ability. And you can tell that the FP community has mostly come to the same conclusion, because this “solution” has been well-known for some time.

So I would suggest not trying to change Functor to fit your code, but maybe your code to fit Functor.

One approach you can take is to use Coyoneda, as described by @puffnfresh in How can we map a Set? on typelevel blog.

But there is probably an even better, direct approach: take the Monoid constraint off the type parameter to Foo and move it to the operations that need it. (If map is one of those, then you really don’t have a Functor after all, not in this category anyway.) Consider, for example, a TreeMap: while Ordering of the type parameter is needed to look up keys, it is not required to count or fold over the entries.

abstract class Foo[A] {
  val a: A // don't need it for this

  def map[B](f: A => B): Foo[B] // hope you don't need it for this

  // but maybe you need it for this
  def append(o: Foo[A])(implicit A: Monoid[A]): Foo[A]

  // and so on
}