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 ;-)
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. –
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). –
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? –