Was ist die Motivation für die Bewertung der Scala-Zuordnung zur Einheit und nicht für den zugewiesenen Wert?

83

Was ist die Motivation für die Bewertung der Scala-Zuordnung zur Einheit und nicht für den zugewiesenen Wert?

Ein gängiges Muster bei der E / A-Programmierung ist Folgendes:

while ((bytesRead = in.read(buffer)) != -1) { ...

Dies ist in Scala jedoch nicht möglich, weil ...

bytesRead = in.read(buffer)

.. gibt Unit zurück, nicht den neuen Wert von bytesRead.

Es scheint interessant zu sein, eine funktionale Sprache wegzulassen. Ich frage mich, warum es so gemacht wurde?

Graham Lea
quelle
David Pollack hat einige Informationen aus erster Hand veröffentlicht, die durch den Kommentar, den Martin Odersky selbst zu seiner Antwort hinterlassen hat, ziemlich bestätigt werden. Ich denke, man kann Pollacks Antwort sicher akzeptieren.
Daniel C. Sobral

Antworten:

87

Ich habe mich dafür eingesetzt, dass Zuweisungen den zugewiesenen Wert und nicht die Einheit zurückgeben. Martin und ich gingen hin und her, aber sein Argument war, dass es eine Verschwendung von Byte-Codes ist, einen Wert auf den Stapel zu setzen, um ihn zu 95% der Zeit zu entfernen, und sich negativ auf die Leistung auswirkt.

David Pollak
quelle
7
Gibt es einen Grund, warum der Scala-Compiler nicht prüfen konnte, ob der Wert der Zuweisung tatsächlich verwendet wird, und dementsprechend einen effizienten Bytecode generieren konnte?
Matt R
43
In Gegenwart von Setzern ist es nicht so einfach: Jeder Setter muss ein Ergebnis zurückgeben, was schwer zu schreiben ist. Dann muss der Compiler es wegoptimieren, was bei Aufrufen schwierig ist.
Martin Odersky
1
Ihr Argument macht Sinn, aber Java & C # sind dagegen. Ich denke, Sie machen etwas Seltsames mit dem generierten Bytecode. Wie würde dann eine Zuweisung in Scala aussehen, die in eine Klassendatei kompiliert und die zurück in Java dekompiliert wird?
Phương Nguyễn
3
@ PhươngNguyễn Der Unterschied ist das Prinzip des einheitlichen Zugriffs. In C # / Java kehren Setter (normalerweise) zurück void. In Scala foo_=(v: Foo)sollte zurückkehren, Foowenn die Zuordnung dies tut.
Alexey Romanov
5
@ Martin Odersky: Wie wäre es mit folgendem: Setter bleiben void( Unit), Zuweisungen x = valuewerden in Äquivalente von übersetzt x.set(value);x.get(value); Der Compiler eliminiert in Optimierungsphasen die getAufrufe, wenn der Wert nicht verwendet wurde. Es könnte eine willkommene Änderung in einer neuen Hauptversion von Scala (aufgrund von Abwärtsinkompatibilität) und weniger Irritationen für Benutzer sein. Was denken Sie?
Eugen Labun
20

Ich bin nicht mit Insiderinformationen über die tatsächlichen Gründe vertraut, aber mein Verdacht ist sehr einfach. Scala macht die Verwendung von Nebenwirkungsschleifen umständlich, so dass Programmierer natürlich das Verständnis bevorzugen.

Dies geschieht auf viele Arten. Zum Beispiel haben Sie keine forSchleife, in der Sie eine Variable deklarieren und mutieren. Sie können den Status eines (nicht einfach) mutierenwhile Schleife gleichzeitig mit dem Testen der Bedingung bedeutet, dass Sie die Mutation häufig unmittelbar vor und am Ende wiederholen müssen. In einem whileBlock deklarierte Variablen sind in der whileTestbedingung nicht sichtbar , was do { ... } while (...)viel weniger nützlich ist. Und so weiter.

Problemumgehung:

while ({bytesRead = in.read(buffer); bytesRead != -1}) { ... 

Für was auch immer es wert ist.

Als alternative Erklärung musste sich Martin Odersky vielleicht einigen sehr hässlichen Fehlern stellen, die sich aus einer solchen Verwendung ergaben, und beschloss, sie aus seiner Sprache zu verbannen.

BEARBEITEN

David Pollack hat geantwortet mit einigen tatsächlichen Fakten , die eindeutig durch die Tatsache bestätigt werden, dass Martin Odersky selbst seine Antwort kommentierte und dem von Pollack vorgebrachten Argument der leistungsbezogenen Fragen Glauben schenkt.

Daniel C. Sobral
quelle
3
Vermutlich wäre die forLoop-Version also: Das for (bytesRead <- in.read(buffer) if (bytesRead) != -1ist großartig, außer dass es nicht funktioniert, weil es keine gibt foreachund withFilterverfügbar ist!
oxbow_lakes
12

Dies geschah als Teil von Scala mit einem "formal korrekteren" Typsystem. Formal gesehen ist die Zuweisung eine rein nebensächliche Aussage und sollte daher zurückkehren Unit. Dies hat einige nette Konsequenzen; beispielsweise:

class MyBean {
  private var internalState: String = _

  def state = internalState

  def state_=(state: String) = internalState = state
}

Die state_=Methode gibt Unit(wie für einen Setter zu erwarten) genau deshalb zurück, weil die Zuweisung zurückkehrt Unit.

Ich bin damit einverstanden, dass für C-Muster wie das Kopieren eines Streams oder ähnliches diese spezielle Entwurfsentscheidung etwas mühsam sein kann. Es ist jedoch im Allgemeinen relativ unproblematisch und trägt wirklich zur Gesamtkonsistenz des Typsystems bei.

Daniel Spiewak
quelle
Danke, Daniel. Ich denke, ich würde es vorziehen, wenn die Konsistenz wäre, dass sowohl Zuweisungen als auch Setter den Wert zurückgeben würden! (Es gibt keinen Grund, warum sie das nicht können.) Ich vermute, dass ich die Nuancen von Konzepten wie einer "rein nebenwirkenden Aussage" noch nicht beherrsche.
Graham Lea
2
@Graham: Aber dann müssten Sie der Konsistenz folgen und sicherstellen, dass alle Ihre Setter, so komplex sie auch sein mögen, den von ihnen festgelegten Wert zurückgeben. Dies wäre in einigen Fällen kompliziert und in anderen Fällen einfach falsch, denke ich. (Was würden Sie im Fehlerfall zurückgeben? Null? - eher nicht. Keine? - dann lautet Ihr Typ Option [T].) Ich denke, es ist schwierig, damit übereinzustimmen.
Debilski
7

Vielleicht liegt das am Prinzip der Befehl-Abfrage-Trennung ?

CQS ist in der Regel an der Schnittstelle von OO- und funktionalen Programmierstilen beliebt, da es eine offensichtliche Unterscheidung zwischen Objektmethoden schafft, die Nebenwirkungen haben oder nicht (dh das Objekt verändern). Das Anwenden von CQS auf Variablenzuweisungen geht weiter als gewöhnlich, aber die gleiche Idee gilt.

Eine kurze Darstellung von warum CQS ist nützlich: eine hypothetische Hybrid - F / OO - Sprache mit einer Betrachten ListKlasse , die Methoden hat Sort, Append, First, und Length. Im imperativen OO-Stil möchte man möglicherweise eine Funktion wie die folgende schreiben:

func foo(x):
    var list = new List(4, -2, 3, 1)
    list.Append(x)
    list.Sort()
    # list now holds a sorted, five-element list
    var smallest = list.First()
    return smallest + list.Length()

Während man in einem funktionaleren Stil eher so etwas schreiben würde:

func bar(x):
    var list = new List(4, -2, 3, 1)
    var smallest = list.Append(x).Sort().First()
    # list still holds an unsorted, four-element list
    return smallest + list.Length()

Diese scheinen es zu versuchen , dasselbe zu tun, aber offensichtlich ist eine der beiden falsch, und ohne mehr über das Verhalten der Methoden zu wissen, können wir nicht sagen, welche.

Bei Verwendung von CQS würden wir jedoch darauf bestehen, dass wenn Append und Sortdie Liste ändern, müssen sie den Einheitentyp zurückgeben, damit wir verhindern , dass Fehler zu schaffen , indem die zweite Form zu verwenden , wenn wir nicht sollten. Das Vorhandensein von Nebenwirkungen wird daher auch in der Methodensignatur impliziert.

CA McCann
quelle
4

Ich würde vermuten, dass dies dazu dient, das Programm / die Sprache frei von Nebenwirkungen zu halten.

Was Sie beschreiben, ist die absichtliche Verwendung einer Nebenwirkung, die im allgemeinen Fall als eine schlechte Sache angesehen wird.

Jens Schauder
quelle
Heh. Scala frei von Nebenwirkungen? :) Auch, stellen Sie sich einen Fall wie val a = b = 1(man stelle sich „magische“ valvor b) vs. val a = 1; val b = 1;.
Dies hat nichts mit Nebenwirkungen zu tun, zumindest nicht in dem hier beschriebenen Sinne: Nebenwirkung (Informatik)
Feuermurmel
4

Es ist nicht der beste Stil, eine Zuweisung als booleschen Ausdruck zu verwenden. Sie führen zwei Dinge gleichzeitig aus, was häufig zu Fehlern führt. Und die versehentliche Verwendung von "=" anstelle von "==" wird mit der Einschränkung von Scalas vermieden.

Deamon
quelle
2
Ich denke das ist ein Müllgrund! Während das OP veröffentlicht wird, wird der Code immer noch kompiliert und ausgeführt: Er tut einfach nicht das, was Sie vernünftigerweise erwarten. Es ist ein Gotcha mehr, nicht einer weniger!
oxbow_lakes
1
Wenn Sie so etwas wie if (a = b) schreiben, wird es nicht kompiliert. So kann zumindest dieser Fehler vermieden werden.
Deamon
1
Das OP verwendete nicht '=' anstelle von '==', sondern beide. Er erwartet, dass die Zuweisung einen Wert
zurückgibt,
@deamon: Es wird kompiliert (zumindest in Java), wenn a und b boolesch sind. Ich habe Neulinge gesehen, die mit if (a = true) in diese Falle geraten sind. Ein Grund mehr, das einfachere if (a) zu bevorzugen (und klarer, wenn ein signifikanterer Name verwendet wird!).
PhiLho
2

Übrigens: Ich finde den anfänglichen While-Trick auch in Java dumm. Warum nicht so etwas?

for(int bytesRead = in.read(buffer); bytesRead != -1; bytesRead = in.read(buffer)) {
   //do something 
}

Zugegeben, die Zuweisung wird zweimal angezeigt, aber zumindest bytesRead befindet sich in dem Bereich, zu dem sie gehört, und ich spiele nicht mit lustigen Zuweisungstricks ...

Landei
quelle
1
Obwohl Trick ziemlich häufig ist, erscheint er normalerweise in jeder App, die einen Puffer liest. Und es sieht immer aus wie die Version von OP.
TWiStErRob
0

Sie können eine Problemumgehung dafür haben, solange Sie einen Referenztyp für die Indirektion haben. In einer naiven Implementierung können Sie Folgendes für beliebige Typen verwenden.

case class Ref[T](var value: T) {
  def := (newval: => T)(pred: T => Boolean): Boolean = {
    this.value = newval
    pred(this.value)
  }
}

Anschließend können Sie unter der Einschränkung, die Sie verwenden müssen, um anschließend ref.valueauf die Referenz zuzugreifen, Ihr whilePrädikat als schreiben

val bytesRead = Ref(0) // maybe there is a way to get rid of this line

while ((bytesRead := in.read(buffer)) (_ != -1)) { // ...
  println(bytesRead.value)
}

und Sie können die Prüfung bytesReadimpliziter durchführen, ohne sie eingeben zu müssen.

Debilski
quelle