Seltsamer Typkonflikt bei Verwendung des Mitgliedszugriffs anstelle des Extraktors
-
09-12-2019 - |
Frage
Gegeben sei ein Tupel mit Elementen vom Typ A
und ein anderer Typ parametrisiert in A
:
trait Writer[-A] { def write(a: A): Unit }
case class Write[A](value: A, writer: Writer[A])
Und eine Verwendungsseite:
trait Cache { def store[A](value: A, writer: Writer[A]): Unit }
Warum funktioniert Folgendes wie erwartet mit dem Extraktor des Tupels:
def test1(set: Set[Write[_]], cache: Cache): Unit =
set.foreach {
case Write(value, writer) => cache.store(value, writer)
}
Aber Folgendes schlägt fehl:
def test2(set: Set[Write[_]], cache: Cache ): Unit =
set.foreach { write =>
cache.store(write.value, write.writer)
}
mit Fehlermeldung
found : Writer[_$1] where type _$1
required: Writer[Any]
cache.store(write.value, write.writer)
^
Kann ich das zweite Formular reparieren (test2
) richtig kompilieren?
BEARBEITEN
Ausgehend von den Ideen von Owen habe ich versucht, es ohne Mustervergleich zum Laufen zu bringen (was ich eigentlich wollte).Hier sind zwei weitere seltsame Fälle, einer funktioniert, der andere nicht:
// does not work
def test3(set: Set[Write[_]], cache: Cache): Unit = {
def process[A](write: Write[A]): Unit =
cache.store(write.value, write.writer)
set.foreach(process)
}
// _does work_
def test4(set: Set[Write[_]], cache: Cache): Unit = {
def process[A](write: Write[A]): Unit =
cache.store(write.value, write.writer)
set.foreach(w => process(w))
}
Für mich immer noch ziemlich unklar...
Lösung
Laufen mit -Xprint:typer
ist hier erhellend.Das Problem mit test2
ist, dass es einen existenziellen Typ gibt, der an zwei getrennten Stellen erscheint:beidewrite.value
Und write.writer
einen existenziellen Typ haben, aber vor allem hat der Compiler keine Möglichkeit zu wissen, dass sie das haben Dasselbe existenziell quantifizierte Typvariable.Obwohl Sie aus demselben Objekt auf sie zugreifen, vergisst der Compiler, dass er vom selben Ort kamen.
Wann test1
vollständig typisiert ist, sehen Sie:
def test1(set: Set[Write[_]], cache: Cache) =
set.foreach(((x0$1: Write[_]) => x0$1 match {
case (value: _$1, writer: Writer[_$1])Write[_$1]((value @ _), (writer @ _)) =>
cache.store[_$1](value, writer)
}));
Die Typvariable _$1
wird zusammen mit den Werten abgeglichen.Passend zur Typvariablen _$1
bindet es in den Geltungsbereich der case
, Es ist also nicht mehr existenziell, und Scala kann das sagen value
Und writer
haben den gleichen Typparameter.
Die Lösung für test2
ist, keine Existentialien zu verwenden:
def test2[A]( set: Set[ Write[ A ]], cache: Cache ) {
set.foreach { write =>
cache.store( write.value, write.writer )
}
}
oder um die Typvariable mit einer Übereinstimmung zu binden:
def test2( set: Set[ Write[ _ ]], cache: Cache ) {
set.foreach { case write: Write[a] =>
cache.store( write.value, write.writer )
}
}
bearbeiten
Lassen Sie mich versuchen, die neuen Fragen zu beantworten, die Sie aufgeworfen haben.
Der Grund test3
funktioniert nicht, ist das, wenn Sie schreiben:
set.foreach( Prozess)
process
, das polymorph ist, muss aus zwei Gründen (die mir bekannt sind) monomorph gemacht werden:
Funktionen in Scala können nicht polymorph sein;Nur Methoden können sein.
process
als Methode definiert;Wenn es als erstklassige Funktion verwendet wird, ist es eine Funktion.Die Art und Weise, wie der Compiler die Typinferenz durchführt, besteht hauptsächlich darin, polymorphe Werte zu nehmen und sie zu vereinheitlichen, um sie weniger polymorph zu machen.Die Übergabe eines tatsächlichen polymorphen Werts als Methodenargument würde höherrangige Typen erfordern.
Der Grund dass test4
tut Die Arbeit besteht darin, dass das Funktionsliteral:
set.foreach( w => process( w ))
ist eigentlich keine polymorphe Funktion!Es nimmt als Argument einen exessentiell qualifizierten Typ;aber kein polymorpher Typ.Es ruft dann das auf Methode (nicht die Funktion) process
, und stimmt mit der existenziellen Typvariable überein process
Typparameter.Ziemlich wild, oder?
Sie könnten auch schreiben:
set.foreach( process(_) )
was beim Erstellen einer anonymen Funktion dasselbe bedeutet.
Eine andere Route, die Sie möglicherweise angemessen empfinden oder nicht, ist die Verurteilung von existenziellen Typen und die Verwendung von Typenelementen:
trait Writable {
type A
val value: A
val writer: Writer[A]
}
case class Write[T]( value: T, writer: Writer[ T ]) extends Writable {
type A = T
}
def test2( set: Set[Writable], cache: Cache ) {
set.foreach { write =>
cache.store( write.value, write.writer )
}
}
Hier kann Scala das sehen write.value
Und write.writer
Haben Sie den gleichen Typparameter, da sie den gleichen Pfad -abhängigen Typ haben.