2014-03-13 18 views
7

Wie kann ich erzwingen, dass trickyMethod die Argumente zur Kompilierzeit gleich sind, aber zur gleichen Zeit auch die gemeinsame Super haben geben Sie Fruit ein?Scala Puzzle: Erzwingen, dass zwei Funktionsargumente vom gleichen Typ sind und beide sind ein Subtyp einer gegebenen Klasse

Also mit anderen Worten, tricky.trickyMethod(new Banana,new Apple) sollte nicht kompilieren.

Ich bin sicher, es muss eine einfache Lösung sein, aber ich habe nur 1 Stunde nach der Antwort gesucht und habe noch keine Ahnung :(

Ich versuchte impliziten Beweis mit <: < aber ich konnte es nicht an der Arbeit

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:.!

Vielen Dank für die Antworten

Follow-up (allgemeiner) Frage:

Dieses zweite Beispiel ist ein allgemeiner Fall des ersten Beispiels.

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 

} 
+0

Sicherlich wollen Sie die allgemeinen Parameter auf 'trickyMethod' gehen statt' TrickyClass'? Sie erlauben ausdrücklich, dass 'T' in der Definition von' tricky' 'Fruit' ist. – Lee

+0

Ja, das ist meine Absicht. (Dies ist ein einfaches Beispiel, das das gleiche Problem aufweist, das bei einem komplizierteren Problem auftauchte.) – jhegedus

Antwort

7

Sie könnten tun:

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]] = [email protected] 

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)) 
+1

Danke! Ich könnte sogar diese 2 Zeichen kürzer schreiben: def trickyMethod [S1, S2 <: T] (p1: S1, p2: S1) (implizit ev: S1 =: = S2) = println ("Ich bin schwierig zu lösen!") Groß! Diese einfache Lösung beantwortet beide Fragen. – jhegedus

+0

Für jeden, der so verwirrt ist wie ich über "=: =" - es ist in Predef. –

3

Sie können dies mit implicits so tun. Denken Sie daran, dass Sie immer in der Lage sein werden,

seit dem Verbot, dass die Subtyping-Beziehung brechen würde. (Wenn wirklich nötig, könnten Sie Miles Sabin's encoding of "not this type" verwenden (siehe # 38) Kommentar Fruit abzulehnen.)

eine Art und Weise Wie auch immer, zu erreichen, was Sie wollen, ist wie folgt:

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] 

Jetzt haben wir eine implizite, dass wird uns ein Dummy-Objekt geben, das bestätigt, dass zwei Typen gleich sind.

So, jetzt ändern wir TrickyClass zu

class TrickyClass[T <: Fruit] { 
    def trickyMethod[A <: T, B <: T](p1: A, p2: B)(implicit same: SamePair[A,B]) = println("!") 
} 

Und es tut, was Sie wollen:

scala> val tricky=new TrickyClass[Fruit]() 
tricky: TrickyClass[Fruit] = [email protected] 

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 
+0

Schöne Antwort. Und Sie haben gut daran getan, vor der Tatsache zu warnen, dass Untertyp-Beziehungen bedeuten, dass wir den Aufrufer nicht wirklich daran hindern können, zwei Instanzen mit unterschiedlichen (Laufzeit-) Typen zu übergeben. Aber Ihre Formulierung sieht so aus, als ob Sie Miles Sabins Kodierung von "nicht diesem Typ" verwenden würde, um das Problem zu verhindern (aber es wäre kein Werkzeug, das eine andere Möglichkeit ermöglicht, im Wesentlichen dieselben Einschränkungen zu schreiben wie hier manuell) –

+0

@ RégisJean -Gilles - Du kannst damit "nicht' Fruit' "kodieren. Das ist alles. Nicht "das sind die gleichen", nur "nicht' Fruit' ". Wenn alles, was nicht "Fruit" ist, eine Blattklasse ist, reicht das (zusätzlich zur "gleichen" Kodierung). Sonst haben Sie kein Glück, je nachdem, was Sie wollen. –

+0

Sicher, wenn es sich um Leaf-Klassen handelt, geht das Problem weg (sowohl mit der Encodierung der Constraints als auch mit Miles Sabins Encoding von "not this type"). Mein Punkt war nur, dass umgekehrt, wenn es sich nicht um Blattklassen handelt, das Problem der Untertypisierung in beiden Codierungen gleich ist. –

1

Sie implicits nutzen könnten, obwohl Sie eine für jede Fruit Unterklasse definieren müssen:

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 
} 
3

Versuchen Sie etwas wie dieses:

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 
} 

es gibt:

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 

Auf der letzten.

+1

Dies ist die einzige Antwort, die eine Stimme von mir bekommt - viel sauberer als impliziert, und ein gemeinsames Muster in Java-Generika. –

+0

Kann diese Lösung verallgemeinert werden, um das zweite (allgemeinere) Problem zu lösen? – jhegedus

+3

@EdStaub - Ich finde dieses Muster sehr hässlich, da es einen generischen Parameter in "Fruit" einführt, der jetzt überall verbreitet werden muss, der mit ihnen umgeht, und auch keine Sicherheit garantiert, da man 'class LyingFruit extends Fruit [ Apfel] '. – Lee