Scala: Performance A is not a subtype of B

I am trying to overload a method based on whether a parameter extends a given class and what problems arise. Using the Miles Sabin approach , I released 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 leads to ambiguity, and the compiler does not know which of the two methods needs to be called. Iโ€™m also looking for an explanation of why there is ambiguity in this case (unlike other simpler cases outlined in the Miles style), as well as how to get around this obstacle. Note that I need to perform this check at the parameter level (instead of defining one method and doing the check in the body), because I want to have different return types.

+7
scala
source share
1 answer

The first problem is that due to the way you work in REPL, the second foobar just obscures the first. If you want an overloaded definition, you will need to use :paste to define both at once.

This still will not give 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 abbreviated ExtendedGenericTypes because I hate horizontal scrollbars.)

You can even try to explicitly specify an instance of <:< :

 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 happens here is that the compiler will not allow the second list of parameters to determine which overloaded definition to use. This seems to mean that overloaded methods with multiple parameter lists, where the first parameter lists are the same, are essentially useless. Probably the ticket for this is the first glance I can think of, SI-2383 .

None of this matters, because you simply shouldn't use overloaded methods here. Overloading is a terrible "feature" that hangs from Java and breaks all kinds of stuff .

However, there are other possible approaches. Some of my favorite Scala weird tricks rely on the fact that you can provide a default value for an implicit parameter that will be used if the compiler cannot find the 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 am using Either instead of allowing the existence of an implicit definition of the return type. There are ways in which you could do this (for example, Shapeless first- the values โ€‹โ€‹of the polymorphic functions of a class ), but in this case they are likely to be excessive.


Update: good, 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 must think very hard before taking this approach.


Update to update, for completeness: you can also do it more directly (but also in more detail) without Shapeless using dependent types of methods (again, you will need to use :paste to define all this 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 a fairly advanced material and should be used with caution.

+7
source share

All Articles