Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Enforcing A is not a subtype of B

Tags:

scala

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.

like image 999
Kvass Avatar asked Jun 08 '14 04:06

Kvass


1 Answers

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.

like image 180
Travis Brown Avatar answered Nov 15 '22 21:11

Travis Brown