2016-05-03 17 views
1

Ich habe versucht, eine SBT-Build für ein Open-Source-Projekt, das ich gerne aus meinem Projekt verweisen würde, zu generieren, und ich stieß auf, was scheint sei ein Compilerfehler.Workaround für einen scala.language.dynamics Bug in Scala 2.10 Compiler

Der folgende Code kompiliert und ausgeführt wie in Eclipse/scala-ide erwartet, aber der scala 2.10.6 Compiler nicht in der Lage ist, es zu verdauen:

package foo 

import scala.language.dynamics 

object Caller extends App { 
    val client = new Client() // initialise an R interpreter 
    client.x = 1.0 
} 
class Client extends Dynamic { 
    var map = Map.empty[String, Any] 
    def selectDynamic(name: String) = map get name getOrElse sys.error("field not found") 
    def updateDynamic(name: String)(value: Any) { map += name -> value } 
} 

Hier ist mein build.sbt:

scalaVersion := "2.10.6" 

libraryDependencies++= Seq(
    "org.scalanlp" %% "breeze" % "0.12" 
) 

Wenn ich scalaVersion angeben: = 2.10.6, erhalte ich den folgenden Compiler-Fehler:

[error] /home/philwalk/dynsbt/src/main/scala/foo/Caller.scala:8: type mismatch; 
[error] found : foo.Caller.client.type (with underlying type foo.Client) 
[error] required: ?{def x: ?} 
[error] Note that implicit conversions are not applicable because they are ambiguous: 
[error] both method any2Ensuring in object Predef of type [A](x: A)Ensuring[A] 
[error] and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A] 
[error] are possible conversion functions from foo.Caller.client.type to ?{def x: ?} 
[error] client.x = Seq("a","b","c") 
[error] ^
[error] one error found 
[error] (compile:compileIncremental) Compilation failed 
[error] Total time: 3 s, completed May 3, 2016 11:03:08 AM 

Mit s calaVersion: = 2.11.8, keine Probleme, obwohl ich Cross-kompilieren muss, also ist das kein Workaround.

Ein weiterer Hinweis ist, dass ich, indem diese Codezeile das Problem verstecken:

client.x = 1.0 

dazu:

client.xx = 1.0 

Ich sehe auch das Problem, wenn ich direkt mit 2,10 scalac kompilieren. 6.

Als Workaround konnte ich das Projekt so umgestalten, dass Feldnamen verwendet werden, die länger als ein einzelnes Zeichen sind. Obwohl es nicht mein Projekt ist, bin ich etwas eingeschränkt, was ich als Workaround akzeptieren kann. Außerdem ist es ein Projekt von breeze.linalg, und es wäre eine ernsthafte Einschränkung, Matrixzeichen und Vektornamen aus einem Zeichen zu verbieten.

Es dauerte ein paar Stunden, um das Problem auf dieses Codefragment aus einem größeren Projekt zu bringen, und ich würde es vorziehen, der scala 2.10-Version dieser Open-Source-Bibliothek keine Beschränkungen aufzuerlegen. Da dieser Fehler in scala 2.11 behoben wurde, gehe ich davon aus, dass der Fehler nicht auf 2.10 zurückportiert wurde.

Ich änderte den Titel, um das Vorhandensein einer Problemumgehung (längerer Feldname) widerzuspiegeln.

+0

Erhalten Sie den Fehler, wenn Sie sauber ausführen und dann kompilieren? – Martin

+0

Ja, es ist ziemlich stabil und wiederholbar. Sie können den Quellcode auch einfach in eine Datei unter src/main/scala/foo/Client.scala einfügen und dann versuchen, mit sbt 0.13.11 zu kompilieren (standardmäßig scala 2.10). 6, wie es passiert). – philwalk

+1

Ich habe versucht, direkt scalac 2.10.6 laufen, und ich bekomme den gleichen Fehler, also denke ich, das ist nicht schuld. Führen Sie in sbt 'compile', dann' last' aus, um die genauen Parameter zu erhalten, die an scalac übergeben wurden. Edit: Das gleiche in SCALA REPL, Sie brauchen nicht die Bibliothek Abhängigkeit BTW. – Martin

Antwort

2

Dies ist Scala 2.10 Fehler, nicht sbt's.

Das Problem ist, dass in Predef, deren Inhalt in jeder einzelnen Scala-Datei importiert wird, gibt es zwei problematische Klassen: Ensuring und ArrowAssoc. Die Mitglieder dieser zwei Klassen sind durch implizite Umwandlungen unter beliebigen Werttypen verfügbar. ArrowAssoc zum Beispiel ist der Grund, warum Sie 1 -> 2 tun können, um ein Tupel (1, 2) zu konstruieren.

Nun hatten diese Klassen, in 2.10, die sehr unglückliche Eigenschaft, ein Mitglied mit dem Namen x zu deklarieren! Obwohl es in 2.10 veraltet ist, gibt es ein ernstes Problem für die Verwendung von scala.Dynamic.

in Ihrem Code client.x = 1.0 prüft zuerst, ob client.x als val/Getter auf client ‚s Art existiert. Es ist nicht wirklich, aber es wäre verfügbar, wenn wir die impliziten Konvertierungen in Predef verwendet, um es in Ensuring oder ArrowAssoc zu konvertieren.Da implizite Konvertierungen eine höhere Priorität als die selectDynamic Behandlung haben, versucht der Scala-Compiler sie zu verwenden. Da es jedoch zwei gleichwertige Conversions gibt, sind sie nicht eindeutig und Sie erhalten einen Kompilierungsfehler.

Zusammengefasst ist dies eine unglückliche Folge von 2 Fakten:

  • Es gibt implizite Konvertierungen ein Mitglied Bereitstellung x auf alles
  • Diese Umwandlung Vorrang vor der Dynamic Behandlung hat.

Die Art und Weise, diese zu lösen, in Ihrem Fall ist ausdrücklich x in Client zu erklären, als Forwarder zu selectDynamic und updateDynamic:

class Client extends Dynamic { 
    var map = Map.empty[String, Any] 
    def selectDynamic(name: String) = map get name getOrElse sys.error("field not found") 
    def updateDynamic(name: String)(value: Any) { map += name -> value } 

    // Work around the annoying implicits in Predef in Scala 2.10. 
    def x: Any = selectDynamic("x") 
    def x_=(value: Any): Unit = updateDynamic("x")(value) 
} 

Nun client.x wird natürlich nutzen die ausdrücklich x erklärt und sein Setzer x_= in Client, der an selectDynamic/updateDynamic delegieren wird.

+0

Danke, es scheint, ich kann meinen Kuchen essen und es auch essen :) – philwalk