Scala Puzzle: using two function arguments of the same type AND both are subtypes of this class

How can I ensure that the arguments to trickyMethod are the same at compile time, but at the same time also have a common super type Fruit ?

In other words, tricky.trickyMethod(new Banana,new Apple) should not be compiled.

I'm sure there should be a simple solution, but I just spent 1 hour searching for an answer and still don't know :(

I tried implicit evidence with <: <but I couldn't 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:

Thanks for answers!

The following (more general) question:

This second example is a more general example 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 } 
+7
types scala compilation type-inference type-systems
source share
4 answers

You can 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)) 
+7
source share

You can do it with implications. Keep in mind that you can always

 tricky.trickyMethod((new Banana): Fruit, (new Apple): Fruit) 

since the ban will violate the subtyping relationship. (If it is really necessary, you can use the Miles Sabin encoding "not this type" (see Comment # 38) to reject Fruit .)

In any case, 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 one that will give us a dummy object that checks that the 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 he 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 
+3
source share

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 } 

He gives:

 error: inferred type arguments [Fruit[_ >: Apple with Banana <: Fruit[_ >: Apple with Banana <: ScalaObject]]] do not conform to method trickyMethod type parameter bounds [T <: Fruit[T]] tricky.trickyMethod(new Banana,new Apple) //this should NOT compile 

At the last.

+3
source share

You can use implicits, although you will need to define one for each subclass of Fruit :

 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 } 
+1
source share

All Articles