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

War es hilfreich?

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:

  1. 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.

  2. 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 processTypparameter.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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top