I find extension methods in type classes a little bit cumbersome at the moment.
Here’s what I believe is the common way of using them:
trait Functor[F[_]]: extension [A](lhs: F[A]) def map[B](f: A => B): F[B] given Functor[Option] with extension [A](lhs: Option[A]) def map[B](f: A => B) = lhs.map(f)
This suffers from two flaws.
The first one is mostly a matter of opinion: instance declaration sites have to jump through unnecessary hoops. They really don’t care that
map is an extension method, and it makes the code more verbose than necessary. I would personally prefer something that might be a little more unpleasant at the
trait declaration (written once) and a little bit lighter at the instance declaration (written, hopefully, many times).
The second one is probably a bug: it breaks SAM type inference, as pointed out by Nadav Wiener:
trait Semigroup[A]: extension (lhs: A) def combine(rhs: A): A given bad: Semigroup[Int] = _ + _ // Wrong number of parameters, expected: 1
I’ve been toying with a different way (spoiler warning: it ends up being even less practical): an abstract method, and a concrete extension method that proxies calls to the abstract one.
trait Functor[F[_]]: def fmap[A, B](fa: F[A])(f: A => B): F[B] extension [A](lhs: F[A]) def map[B](f: A => B): F[B] = fmap(lhs)(f) given Functor[Option] with def fmap[A, B](oa: Option[A])(f: A => B) = oa.map(f)
It does make instance declaration more pleasant, but runs into a different problem: you can’t have a regular method and an extension one with the same name. This forces me to find two names for a given method, which is basically a show stopper.
Diego Alonso proposed a different strategy - making the extension method syntax non-compulsory in subclasses.