2016-01-07 12 views
5

Beachten Sie Folgendes behalten:Passing um pfadabhängigen Typ nicht abhängig Wert

trait Platform { 
    type Arch <: Architecture 
    def parseArch(str: String): Option[Arch] 
} 

object Platform { 
    def parse(str: String): Option[Platform] = ??? 
} 

trait Architecture 

def main() { 
    def exec(p: Platform)(a: p.Arch) = ??? 

    Platform.parse("ios") 
    .flatMap(p => p.parseArch("arm64").map(a => (p, a))) 
    .flatMap { case (p, a) => exec(p)(a) } // <----- This fails to compile 
} 

exec(p)(a) schlägt mit Fehlermeldung zu kompilieren:

Error:(17, 40) type mismatch;
found : a.type (with underlying type A$A2.this.Platform#Arch)
required: p.Arch .flatMap { case (p, a) => exec(p)(a) }

Von der Fehlermeldung, so scheint es, dass scalac nicht zu Behalte den Wert(), von dem Arch abhängt, und daher entscheidet er sich stattdessen für die Projektion (obwohl ich nicht sicher bin, was A$A2.this bedeutet).

Für das, was es wert ist, die letzte Zeile mit der folgenden Substitution kompiliert:

.flatMap(p => exec(p)(p.parseArch("arm64").get)) 

Ist dies eine Einschränkung in scala Compiler oder vielleicht etwas hier fehlt mir?

Antwort

4

Die einfache Lösung

Ihre beste Wette, wenn sie mit pfadabhängigen Arten handeln, immer um den Besitzer Wert zu halten, weil Scala sonst sehr begrenzte Folgerung und Argumentation Macht hat.

Zum Beispiel Ihr Codebeispiel neu geschrieben werden können als:

Platform.parse("ios") flatMap { 
    p => p.parseArch("arm64").map(exec(p)) 
} 

Es ist allgemein möglich ist, solche Umschreibungen durchzuführen, obwohl der Code oft weniger prägnant und elegant werden wird. Eine gängige Praxis ist die Verwendung abhängiger Funktionen und parametrischer Klassen.

Verwendung abhängige Typen

In Ihrem Beispiel der Code:

Platform.parse("ios").flatMap(p => p.parseArch("arm64").map(a => (p, a))) 

hat Option[(Platform, Platform#Arch)] geben, weil Scala Folgerung nicht die Tatsache zurückhalten kann, dass das Tupel des zweiten Elements auf dem ersten Element abhängt. (Sie erhalten A$A2.this.Platform, weil Sie in einem inneren Zusammenhang Platform deklariert haben.)

Mit anderen Worten, Scalas Tuple2 Typ ist nicht abhängig. Das könnten wir korrigieren unsere eigene Klasse zu machen:

case class DepPair(p: Platform)(a: p.Arch) 

jedoch Scala bietet keine Unterstützung für abhängige Klasse Signaturen noch nicht, und es wird nicht kompiliert. Stattdessen setzen wir ein Merkmal verwenden:

trait Dep { 
    val plat: Platform 
    val arch: plat.Arch 
} 
Platform.parse("ios") 
    .flatMap { p => p.parseArch("arm64").map { a => 
    new Dep { val plat: p.type = p; val arch: p.Arch = a }}} 
    .flatMap { dep => exec(dep.plat)(dep.arch) } 

Hinweis die Zuschreibung auf val plat und val arch, als ohne sie Scala werden versuchen, eine raffinierte Art zu schließen, die Typüberprüfung fehlschlagen lassen.

Wir sind in der Tat an der Grenze dessen, was in Scala (IMHO) sinnvoll ist. Zum Beispiel, wenn wir parametrisiert hätten, wären wir in alle möglichen Probleme geraten.Bemerkenswert:

Error:(98, 15) type mismatch; 
found : Platform => Option[Dep[p.type]] forSome { val p: Platform } 
required: Platform => Option[B] 

Scala folgert eine existentielle Funktionstyp, aber was würden wir gerne ist eigentlich die existentielle Quantifizierung innerhalb der Funktionstyp zu haben. Wir müssen Scala leiten, das zu verstehen, und wir am Ende mit etwas wie:

Platform.parse("ios").flatMap[Dep[p.type] forSome { val p: Platform }]{ 
    case p => p.parseArch("arm64").map{case a: p.Arch => 
     new Dep[p.type] { val plat: p.type = p; val arch = a }}} 
    .flatMap { dep => exec(dep.plat)(dep.arch) } 

Jetzt werde ich lassen Sie entscheiden, welche Art und Weise ist die beste: Stick mit dem Besitzer val um (einfache Lösung) oder Risiko Du hast jeden Sinn für Vernunft verloren, den du hinterlassen hast!

Aber reden über geistige Gesundheit und Existenziale zu verlieren, lassen Sie uns versuchen und ein bisschen weiter zu untersuchen ...

Mit Existenziale (nicht)

Die problematische Art des Zwischenergebnisses im Code war Option[(Platform, Platform#Arch)]. Es gibt tatsächlich einen Weg, um es besser auszudrücken, eine existentielle verwenden, wie in:

Option[(p.type, p.Arch) forSome {val p: Platform}] 

Wir Scala es explizit durch Angabe helfen kann, so das Zwischenergebnis hat den gewünschten Typ:

val tmp: Option[(p.type, p.Arch) forSome {val p: Platform}] = 
    Platform.parse("ios") 
    .flatMap { case p => p.parseArch("arm64").map { a => (p, a): (p.type, p.Arch) }} 

Wir berühren jetzt jedoch einen sehr sensiblen Bereich des Scala-Systems und es wird oft Probleme verursachen. In der Tat fand ich keinen Weg, um die zweite flatMap ...

Der Versuch tmp.flatMap { case (p, a) => exec(p)(a) } gibt die sehr hilfreich auszudrücken:

Error:(30, 30) type mismatch; 
found : a.type (with underlying type p.Arch) 
required: p.Arch 

Ein weiterer Versuch:

tmp.flatMap { 
    (tup: (p.type, p.Arch) forSome {val p: Platform}) => exec(tup._1)(tup._2) 
} 
Error:(32, 79) type mismatch; 
found : tup._2.type (with underlying type p.Arch) 
required: tup._1.Arch 

Auf diese Ich denke, jede vernünftige Person würde aufgeben - und wahrscheinlich ein paar Tage von der Scala-Programmierung fernbleiben ;-)

+0

Intersting. Ich denke, pfadabhängige Typen sind in Scala immer noch ziemlich rauh. Nachdem ich Ihre Antwort gelesen habe, habe ich es aufgegeben, sie in Tupeln zu übergeben ("Merkmal" -Lösung ist interessant, aber ein wenig zu ausführlich), und stattdessen machte ich eine innere Eigenschaft, einen Bezug auf äußere Merkmale zu halten, wie unten gezeigt. Danke für die Genehmigung meiner Zweifel. –

+0

Großartig. Beachte, dass du deine "Val-Plattform" zu einer "def-Plattform" machen kannst, so dass du keinen zusätzlichen Zeiger auf das äußere Merkmal halten musst (Scala speichert bereits Zeiger auf äußere Merkmale in inneren Merkmalen). –

+0

Wusste nicht den Unterschied zwischen "Def" und "Val" beteiligt Zeiger. Wäre nicht 'def 'gleichermaßen Zeiger auf was auch immer es in seiner _closure_ verweist erstellen (nicht sicher, ob dies eine Sache in der Skala ist)? Vielleicht könnten Sie einen Link teilen? –

3

Ich habe gelernt, die Strombegrenzung der scala-Compiler zu bestätigen (wie von LP ‚s Antwort gezeigt) und kam stattdessen mit dieser Problemumgehung oben:

trait Platform { 
    trait Architecture { 
    val platform: Platform.this.type = Platform.this 
    } 

    object Architecture { 
    def parse(str: String): Option[Architecture] = ??? 
    } 
} 

object Platform { 
    def parse(str: String): Option[Platform] = ??? 
} 

def main() { 
    def exec(a: Platform#Architecture) = { 
    val p = a.platform 
    ??? 
    } 

    Platform.parse("ios") 
    .flatMap(p => p.parseArch("arm64")) 
    .flatMap(a => exec(a)) 
} 

Zum Glück kann innere Eigenschaft auf äußerees Merkmal beziehen in Scala. Auf diese Weise ist es nicht notwendig, und p.Arch zusammen zu passieren, stattdessen hat jeder a: Platform#Architecture Verweis auf eigene p: Platform.