this is a direct translation of my scala2 code to scala3
trait Narrow[F[_], A, B <: A: ClassTag]:
def apply(fa: F[A]): F[B]
extension [F[_], A] (fa: F[A]):
def narrow[B: ClassTag] (using op: Narrow[F, A, B]): F[B] = op(fa)
I need to specify the type of the narrow operation at the call site, however extension methods do not allow that syntax. what are there the best workarounds for this limitation?
the purpose of this is to be able to narrow the type in a collection / try / whatever. the narrow type class will flatmap whatever is inside, compare the runtime type, if it matches wrap that B in an F, or otherwise return an empty F
trait A
trait B extends A
object A extends A
object B extends B
val bb: List[B] = List(A, A, B, B, A)
.narrow[B]
assert(bb == List(B, B))
You could use a polymorphic function, if you can handle the ugliness:
extension [F[_], A] (fa: F[A]):
def narrow() = [B <: A] => (using op: Narrow[F, A, B]) => op(fa)
You can then call it with foo.narrow()[String]
. Here it is in Scastie.
The narrow()
is necessary, because without it, the type argument would go to the extension and not the polymorphic function.
In the future, Scala 3 may allow type arguments directly to methods inside extensions, but right now, you can keep using your Scala 2 implicit class and change it after the next release:
implicit class NarrowOps[F[_], A](fa: F[A]):
def narrow[B <: A](using op: Narrow[F, A, B]) = op(fa)
Side note: You don't need B: ClassTag
again in your extension, although I believe you do need to use the bound B <: A
.