Splitting from this comment
Occasionally an extension method will need to be written that is generic on two axes: the anchoring type and a type argument to the method itself. A simple example of this is the pure
method on cats.Monad
.
This is representable in Scala 2 in a fairly straightforward manner:
implicit final class Lifts[A](val a: A) extends AnyVal {
def pure[F[_]](implicit P: Pure[F]): F[A] = P.pure(a)
}
An expanded live example can be found here.
This does not appear to be representable with the new extension method syntax. I tried a couple different approaches, and have annotated the following code snippet with the errors caused by uncommenting the various attempts:
trait Pure[F[_]] {
def pure[A](a: A): F[A]
extension [A] (a: A) def pureOne: F[A] = pure(a)
}
object Pure {
given Pure[List] {
def pure[A](a: A): List[A] = a :: Nil
}
extension [A,F[_]: Pure] (a: A) def pureTwo: F[A] = summon[Pure[F]].pure(a)
final class PartiallyAppliedPureThree[A](val a: A) extends AnyVal {
def apply[F[_]: Pure] = summon[Pure[F]].pure(a)
}
extension [A] (a: A) def pureThree: PartiallyAppliedPureThree[A] = new PartiallyAppliedPureThree[A](a)
}
def trialOne() = {
// Without the explicit import of givens, fails with:
// "value pureE is not a member of Int"
import Pure.{given _}
// Fails with:
// value pureE is not a member of Int.
// An extension method was tried, but could not be fully constructed:
//
// Pure.given_Pure_List.extension_pureE[List](1)
//println(1.pureOne[List])
// Fails with:
// Found: (1 : Int)
// Required: List
//println(Pure.given_Pure_List.extension_pureOne[List](1))
}
def trialTwo() = {
// Without explict import of method, fails with:
// value pureTwo is not a member of Int
import Pure.pureTwo
// Fails with:
// value pureTwo is not a member of Int.
// An extension method was tried, but could not be fully constructed:
//
// Pure.extension_pureTwo[List](1)
//println(1.pureTwo[List])
// Works if called explicitly with explicit type parameters
println(Pure.extension_pureTwo[Int,List](1))
}
def trialThree() = {
// Without explicit import of method, fails with:
// value pureThree is not a member of Int
import Pure._
// Fails with:
// value pureThree is not a member of Int.
// An extension method was tried, but could not be fully constructed:
//
// Pure.extension_pureThree[List](1)
//println(1.pureThree[List])
// Fails with:
// Found: (1 : Int)
// Required: List
// println(Pure.extension_pureThree[List](1))
// Works, if called explicity with the type parameter second
println(Pure.extension_pureThree(1)[List])
// More explicit version of the preceding call
println(Pure.extension_pureThree(1).apply[List])
println(1.pureThree.apply[List])
}
@main
def run(): Unit = {
trialOne()
trialTwo()
trialThree()
}
A live version can be found here
Additional examples:
The official example for Functor
also displays this behavior, and creates a really unpleasant experience for folks who are used to the Scala 2.0 Apis.
In this lightly modified example, the first of these work, and the third fails:
List(1,2,3).fmap(_.toString)
List(1,2,3).fmap(Some(_))
List(1,2,3).fmap[Option[Int]](Some(_))
If you want to make sure you get a List[Option[Int]]
instead of a List[Some[Int]]
, you need to either specify both the input and output types, or explicitly type the results:
List(1,2,3).fmap[Int, Option[Int]](Some(_))
List(1,2,3).fmap(Some(_)): List[Option[Int]]
This should probably be at least documented.