Reputation: 2589
Specifically, I'm trying to extend my Functor
typeclass with Applicative
.
trait Functor[F[_]] {
def fmap[A, B](r: F[A], f: A => B): F[B]
}
object Functor {
implicit class FunctorOps[A, F[_]: Functor](xs: F[A]) {
def fmap[B](f: A => B): F[B] = implicitly[Functor[F]].fmap(xs, f)
}
implicit def SeqFunctor: Functor[Seq] = new Functor[Seq] {
def fmap[A, B](r: Seq[A], f: A => B) = r map f
}
}
trait Applicative[F[_]] extends Functor[F] {
// What I want to do, but this *does not* work.
def fmap[A, B](r: F[A], f: A => B): F[B] = Functor.FunctorOps[A, F](r).fmap(f)
def pure[A](x: A): F[A]
def fapply[A, B](r: F[A], f: F[A => B]): F[B]
}
object Applicative {
implicit class ApplicativeOps[A, F[_]](a: F[A])(implicit F: Applicative[F]) {
def fapply[B](f: F[A => B]): F[B] = F.fapply(a, f)
}
implicit def SeqApplicative: Applicative[Seq] = new Applicative[Seq] {
def pure[A](x: A) = Seq(x)
def fapply[A, B](xs: Seq[A], fs: Seq[A => B]): Seq[B] = xs.flatMap(x => fs.map(_(x)))
}
}
The gist of it is I have to implement fmap
for all Applicative
s, but it should really be the same method as defined in my FunctorOps
class. How do I do this in the cleanest way possible?
Upvotes: 0
Views: 108
Reputation: 29193
You got the Applicative[F] <: Functor[F]
part right, but you should really think about what that means. It means that an instance of Applicative[F]
also provides the methods for Functor[F]
. That is, you can't have both implicit val listFunctor: Functor[List]; implicit val listApplicative: Applicative[List]
, because then the compiler is confused when you ask for implicit param: Functor[List]
. You should only have the latter. What you're trying to do is then nonsensical, because you're defining the Functor
instance for F
in terms of itself (because the Applicative[F]
should be the Functor[F]
), and you end up with two Functor[Seq]
s regardless.
What you can do is implement fmap
in terms of pure
and fapply
:
trait Applicative[F[_]] extends Functor[F] {
override def fmap[A, B](r: F[A], f: A => B): F[B] = fapply(r, pure(f))
def pure[A](x: A): F[A]
def fapply[A, B](r: F[A], f: F[A => B]): F[B]
}
Then remove the Functor[Seq]
instance and keep your Applicative[Seq]
the way it is (or override fmap
if you want). You have a different problem now, being that implicit search gets a bit turned around, as the Functor[Seq]
instance is actually in object Applicative
, but fixing implicit resolution is less onerous than enforcing the consistency of separate Functor
and Applicative
instances. In this case, where Seq
is a type whose companion object you cannot control, cats
, at least, does something like
package instances {
trait SeqInstances {
implicit val seqFunctor: Functor[Seq] = ???
}
package object seq extends SeqInstances
package object all extends SeqInstances
with AAAInstances
with BBBInstances
with ...
}
FYI: I suggest currying your typeclass methods
def fmap[A, B](r: F[A])(f: A => B): F[B]
def fapply[A, B](r: F[A])(f: F[A => B]): F[B]
and it may be nice to have a flip fapply
in ApplicativeOps
def ylppaf[I, B](f: F[I])(implicit ev: A =:= (I => B))
: F[B] = F.fapply(a.fmap(ev))(f)
Upvotes: 1