2013-04-30 11 views
7

Ich versuche, eine einfache Abfrage Monad zu schreiben und habe Probleme, meine generischen Typ Anmerkungen richtig zu bekommen.Scala abgeleiteten Typ Argumente - Geben Sie Grenzen auf "Nothing"

Mein erster Versuch ging wie folgt (in beträchtlichem Ausmaß für Prägnanz vereinfacht)

case class Person(val name: String) 
abstract class Schema[T]  
object People extends Schema[Person] 

case class Query[U <: Schema[T], T](schema: U) {  <---- Type signature 
    def results: Seq[T] = ... 
    def where(f: U => Operation) = ... 
} 

class TypeText extends Application { 
    val query = Query(People)      <---- Type inference fails 
} 

Der Compiler dies nicht möchte, da es nicht die Art von ‚T‘ ableiten könnte.

error: inferred type arguments [People.type,Nothing] do not conform to method apply's type parameter bounds [U <: Schema[T],T]

Während des Experimentieren fand ich, dass Ansicht mit Grenzen statt wie erwartet funktioniert

case class Query[U <% Schema[T], T](schema: U) { 

(die Verwendung von Ansicht Hinweis gebunden "<%" anstelle von Typ gebunden "<:")

In meinem begrenzten Verständnis des Typsystems, da ich eine tatsächliche Unterklasse (und nicht nur Konvertierbarkeit) von Schema [T] erwarte, würde ich annehmen, dass der Typ gebunden "<:" ist die richtige Grenze hier zu verwenden?

Wenn dies der Fall ist, was fehlt mir - wie gebe ich dem Compiler genug Hinweise, um T richtig zu schließen, wenn Sie Typgrenzen statt Ansichtsgrenzen verwenden?

Antwort

2

Um die Beziehung zwischen den beiden Typparametern zu kodieren, können Sie so etwas wie

case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... } 

Siehe § 4.3 und § 4.4 der Scala Language Spec für weitere Informationen nutzen.

+0

Danke. Ich hatte diese Syntax gesehen und fragte mich, was das bedeutete. –

+0

Ich glaube nicht, dass diese Antwort richtig ist. Bitte überprüfen Sie meine Antwort am Ende meiner Antwort (dieser Kommentar hätte nicht genügend Platz für die Diskussion zur Verfügung gestellt). –

+0

@ Régis Du hast Recht. Ich habe die Scoping-Regeln in der Spezifikation falsch interpretiert. Es ist in der Tat ein Inferenzproblem, wie Sie in Ihrer Antwort beschrieben haben. Das heißt, die Verwendung eines impliziten Beweisparameters ist der einfachste Weg, die Beziehung zu codieren und die gewünschte Inferenz zu unterstützen. –

1

ich immer gefunden habe, dass, wenn zwei auf einer Klasse/Funktionstyp Bezeichner verwenden, wird die Typinferenz System nicht wie erwartet, und Sie haben wie so explizit sein:

val query = Query[People.type, Person](People) 

Wenn Sie Ihre geändert Query Erklärung dazu:

case class Query[U <: Schema[_](schema: U) 

würden Sie in der Lage sein, dies zu tun:

val query = Query(People) 

Aber dann würden Sie nicht den zugrunde liegenden Typ der Schema geliefert und wäre nicht in der Lage, die results Funktion ordnungsgemäß implementieren.

5

Dies ist keine vollständig statisfisierende Antwort (zumindest für mich), da ich zugeben muss, dass ich nicht genau sagen kann, wo und warum die Schlussfolgerung fehlschlägt. Ich habe nur ein paar unscharfe Intuitionen. Das Problem hängt damit zusammen, dass der Compiler zwei Typparameter gleichzeitig ableiten muss. Warum die Änderung des Typs, der an eine Ansicht gebunden ist, die Kompilierung behebt, ist meines Wissens jetzt, dass es zwei Parameterlisten gibt und dass wir als Ergebnis zwei aufeinanderfolgende Phasen von Typinterferenzen anstelle von zwei Inferenzen gleichzeitig haben. Tatsächlich ist die folgende:

case class Query[U <% Schema[T], T](schema: U) 

ist die gleiche wie:

case class Query[U, T](schema: U)(implicit conv: U => Schema[T]) 

Der erste Parameterliste den Schluß von U treibt, und dann die zweite (beachten Sie, dass U wissen jetzt ist) wird das Laufwerk Schlussfolgerung von T.

Im Fall des Ausdrucks Query(People) der Parameter People den Typ Rückschließer fahren U zu People.type einzustellen. Dann sucht der Compiler nach einer impliziten Konvertierung von People.type nach Schema[T], um die zweite Parameterliste zu übergeben. Der einzige im Geltungsbereich ist die (triviale) Umwandlung von People.type zu Schema[Person], was den Inferenzverweis darauf leitet, dass T = Person.

case class Person(val name: String) 
sealed trait Schema { 
    type T 
} 
abstract class SchemaImpl[_T] extends Schema { 
    type T = _T 
} 
object People extends SchemaImpl[Person] 
case class Query[U <: Schema](schema: U) { 
    def results: Seq[schema.T] = ??? 
} 
class TypeText extends Application { 
    val query = Query(People) 
} 

UPDATE:

@Aaron Novstrup suchen:

die Zusammenstellung zu beheben, ohne verpflichtet zu einer Ansicht zurückgreifen, können Sie den Typ-Parameter T mit einem abstrakten Typ ersetzen Soweit ich weiß, ist Ihre Antwort falsch (Update zum Update: Die ursprüngliche Antwort von Aaron behauptet, dass die Query Deklaration gleich 0 war).

case class Query[U <: Schema[X], T](schema: U) 

kompiliert nicht einmal. Lasst uns sagen, dass Sie

case class Query[U <: Schema[_], T](schema: U) 

gemeint (die tut Kompilierung), ist es einfach, in der REPL zu überprüfen, ob sie entweder nicht die gleiche ist.

Tatsächlich stellt die folgende fein:

case class Query[U <: Schema[_], T](schema: U) 
type MyQuery = Query[Schema[String], Int] 

Während macht folgendes nicht:

case class Query[U <: Schema[T], T](schema: U) 
type MyQuery = Query[Schema[String], Int] 

also den Unterschied zu beweisen. Der Fehler ist:

<console>:10: error: type arguments [Schema[String],Int] do not conform to class Query's type parameter bounds [U <: Schema[T],T] 
     type MyQuery = Query[Schema[String], Int] 

Welche zeigt deutlich, dass das erste und das zweite Vorkommen von T die gleiche Art bezeichnen, und wir tun eine Beziehung zwischen den beiden Typparameter haben.

2

Ich hatte das gleiche Problem. Folgendes funktionierte für mich:

case class Query[U <: Schema[T], T](schema: U with Schema[T]) { 
    ... 
} 
Verwandte Themen