0

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 Applicatives, but it should really be the same method as defined in my FunctorOps class. How do I do this in the cleanest way possible?

1 Answer 1

1

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)
Sign up to request clarification or add additional context in comments.

2 Comments

I want to do what I'm doing based on the idea of extending a given typeclass without changing its source. So: 1) I can't change anything about my Functor code, including removing the implicit class. i.e. what if I get a random typeclass I want to extend? 2) fmap (or whatever function I'm inheriting from the typeclass I'm given) might not be so easy to define in terms of my typeclass's functions.
2) is moot. If you can't define the superclass's methods in terms of the subclass's, then you simply leave them abstract and get the implementor to do it (like in Haskell, where it's the only (portable) way.). 1) If you have some existing, unmodifiable superclass instances, the only sane thing to do is define your subclass instance to defer to the superclass (you can abstract that away in it's own helper class (Helper[F[_]](f: Functor[F]) { ... }), but don't impose that restriction on the subclass itself) and make sure not to import both the subclass and superclass instances together.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.