How can I enforce that trickyMethod
's arguments are the same at compile time but at the same time also have the common super type Fruit
?
So in other words, tricky.trickyMethod(new Banana,new Apple)
should not compile.
I am sure there must be a simple solution but I just spent 1 hour searching for the answer and still have no idea :(
I tried implicit evidence with <:< but I could not get it to work.
class Fruit
class Apple extends Fruit
class Banana extends Fruit
class TrickyClass[T<:Fruit]{
def trickyMethod(p1:T,p2:T)= println("I am tricky to solve!")
}
object TypeInferenceQuestion extends App{
val tricky=new TrickyClass[Fruit]()
tricky.trickyMethod(new Apple,new Apple) //this should be OK
tricky.trickyMethod(new Banana,new Banana) //this should be OK
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
}
EDIT :
Thank you for the answers !
Follow up (more general) question:
This second example is a more general case of the first example.
class Fruit
class Apple extends Fruit
class Banana extends Fruit
class TrickyClass[T]{
def trickyMethod[S<:T](p1:S,p2:S)= println("I am tricky to solve!")
}
object TypeInferenceQuestion extends App{
val tricky=new TrickyClass[Fruit]()
tricky.trickyMethod(new Apple,new Apple) //this should be OK
tricky.trickyMethod(new Banana,new Banana) //this should be OK
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
}
You could do :
class Tricky[T] {
def trickyMethod[S1<:T,S2<:T](s1:S1,s2:S2)(implicit ev: S1=:=S2) = println()
}
scala> val t = new Tricky[Seq[Int]]
t: Tricky[Seq[Int]] = Tricky@2e585191
scala> t.trickyMethod(List(1),List(1))
//OK
scala> t.trickyMethod(List(1),Seq(1))
<console>:10: error: Cannot prove that List[Int] =:= Seq[Int].
t.trickyMethod(List(1),Seq(1))
You can do this with implicits like so. Keep in mind that you will always be able to
tricky.trickyMethod((new Banana): Fruit, (new Apple): Fruit)
since disallowing that would break the subtyping relationship. (If really necessary, you could use Miles Sabin's encoding of "not this type" (see comment #38) to reject Fruit
.)
Anyway, one way to achieve what you want is as follows:
class SamePair[T,U] {}
object SamePair extends SamePair[Nothing,Nothing] {
def as[A] = this.asInstanceOf[SamePair[A,A]]
}
implicit def weAreTheSame[A] = SamePair.as[A]
Now we have an implicit that will give us a dummy object that verifies that two types are the same.
So now we change TrickyClass
to
class TrickyClass[T <: Fruit] {
def trickyMethod[A <: T, B <: T](p1: A, p2: B)(implicit same: SamePair[A,B]) = println("!")
}
And it does what you want:
scala> val tricky=new TrickyClass[Fruit]()
tricky: TrickyClass[Fruit] = TrickyClass@1483ce25
scala> tricky.trickyMethod(new Apple,new Apple) //this should be OK
!
scala> tricky.trickyMethod(new Banana,new Banana) //this should be OK
!
scala> tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
<console>:16: error: could not find implicit value for parameter same: SamePair[Banana,Apple]
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
Try something like this:
abstract class Fruit[T <: Fruit[T]] {
def trick(that: T)
}
class Apple extends Fruit[Apple] {
override def trick(that: Apple) = { println("Apple") }
}
class Banana extends Fruit[Banana] {
override def trick(that: Banana) = { println("Banana") }
}
class TrickyClass{
def trickyMethod[T<:Fruit[T]](p1:T,p2:T)= p1.trick(p2)
}
object TypeInferenceQuestion {
val tricky=new TrickyClass()
tricky.trickyMethod(new Apple,new Apple) //this should be OK
tricky.trickyMethod(new Banana,new Banana) //this should be OK
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
}
it gives:
error: inferred type arguments [Fruit[_ >: Apple with Banana <: Fruit[_ >: Apple with Banana <: ScalaObject]]] do not conform to method trickyMethod's type parameter bounds [T <: Fruit[T]]
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
On the last one.
You could use implicits, although you will have to define one for each Fruit
subclass:
class Fruit
class Apple extends Fruit
class Banana extends Fruit
trait FruitEvidence[T <: Fruit]
class TrickyClass{
def trickyMethod[T <: Fruit](p1:T,p2:T)(implicit e: FruitEvidence[T]) = println("I am tricky to solve!")
}
object FruitImplicits {
implicit val BananaEvidence = new FruitEvidence[Banana] { }
implicit val AppleEvidence = new FruitEvidence[Apple] { }
}
object TypeInferenceQuestion extends App{
import FruitImplicits._
val tricky=new TrickyClass()
tricky.trickyMethod(new Apple,new Apple) //this should be OK
tricky.trickyMethod(new Banana,new Banana) //this should be OK
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
}
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