简体   繁体   中英

Scala: deferring a trait method to an implicit class in parent trait's object

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?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM