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.
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
}
1 Like