I am trying to overload a method based on whether or not a parameter extends a given class, and have been having some trouble. Using an approach by Miles Sabin, I produced the following code:
object ExtendedGenericTypes {
trait <:!<[A, B] // Encoding for "A is not a subtype of B"
// Use ambiguity to rule out the cases we're trying to exclude
implicit def nsubAmbig1[A, B >: A]: A <:!< B = null
implicit def nsubAmbig2[A, B >: A]: A <:!< B = null
// The implicit substitutions
implicit def nsub[A, B]: A <:!< B = null
}
And my use case:
import ExtendedGenericTypes._
class Foo
def foobar[T](x: T)(implicit ev: T <:< Foo) = "hello"
def foobar[T](x: T)(implicit ev: T <:!< Foo) = 5
println(foobar(new Foo()))
Unfortunately, this results in ambiguity, and the compiler does not know which of the two methods to invoke. I'm looking for both an explanation as to why there is ambiguity in this case (as opposed to the other simpler cases outlined in Miles' gist) and also how to circumvent this hurdle. Note that I need to perform this check on the parameter level (rather than defining one method and doing the check in the body) because I want to have different return types.
The first problem is that because of the way you're working in the REPL, the second foobar
simply shadows the first. If you want an overloaded definition, you'll need to use :paste
to define both at once.
That still won't get you what you want, just a new error message:
scala> println(foobar(new Foo))
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo) and expected result type Any
println(foobar(new Foo))
^
(Note that I've abbreviated ExtendedGenericTypes
because I hate horizontal scrollbars.)
You can even try providing the <:<
instance explicitly:
scala> foobar(new Foo)(implicitly[Foo <:< Foo])
<console>:14: error: ambiguous reference to overloaded definition,
both method foobar of type [T](x: T)(implicit ev: EGT.<:!<[T,Foo])Int
and method foobar of type [T](x: T)(implicit ev: <:<[T,Foo])String
match argument types (Foo)
foobar(new Foo)(implicitly[Foo <:< Foo])
^
So what's going on here is that the compiler isn't going to let the second parameter list determine which overloaded definition to use. Which seems to mean that overloaded methods with multiple parameter lists where the first parameter lists are the same are essentially useless. There's probably a ticket for this—at a glance the closest I can come up with is SI-2383.
None of that really matters, though, because you just shouldn't be using overloaded methods here—overloading is a terrible "feature" that's a hangover from Java and breaks all kinds of stuff.
There are other possible approaches, though. Some of my favorite weird Scala tricks rely on the fact that you can provide a default value for an implicit parameter that will be used if the compiler can't find an instance. If I understand correctly, you want something like this:
class Foo
def foobar[T](x: T)(implicit ev: T <:< Foo = null) =
Option(ev).fold[Either[Int, String]](Left(5))(_ => Right("hello"))
case class Bar(i: Int) extends Foo
case class Baz(i: Int)
And then:
scala> foobar(Bar(13))
res0: Either[Int,String] = Right(hello)
scala> foobar(Baz(13))
res1: Either[Int,String] = Left(5)
Note that I'm using an Either
instead of letting the existence of the implicit determine the return type. There are ways you could accomplish that (like Shapeless's first-class polymorphic function values), but they're probably overkill in this case.
Update: okay, since you asked for it:
import shapeless._
trait LowPriorityFoobar { this: Poly1 =>
implicit def anyOld[T] = at[T](_ => 5)
}
object foobar extends Poly1 with LowPriorityFoobar {
implicit def foo[T](implicit ev: T <:< Foo) = at[T](_ => "hello")
}
And then:
scala> foobar(Bar(13))
res6: String = hello
scala> foobar(Baz(13))
res7: Int = 5
No wrapper. You should think really hard before taking this approach, though.
Update to the update, for the sake of completeness: you can also do this more directly (but also more verbosely) without Shapeless using dependent method types (again, you'll need to use :paste
to define these all at once):
class Foo
trait IsFooMapper[I] {
type Out
def apply(i: I): Out
}
trait LowPriorityIsFooMapper {
implicit def isntFoo[A] = new IsFooMapper[A] {
type Out = Int
def apply(a: A) = 5
}
}
object IsFooMapper extends LowPriorityIsFooMapper {
implicit def isFoo[A](implicit ev: A <:< Foo) = new IsFooMapper[A] {
type Out = String
def apply(a: A) = "hello"
}
}
def foobar[A](a: A)(implicit ifm: IsFooMapper[A]) = ifm(a)
And then:
scala> foobar(Bar(13))
res0: String = hello
scala> foobar(Baz(13))
res1: Int = 5
Again, this is fairly advanced stuff and should be used with caution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With