Frage Was ist die Motivation für eine Scala-Zuweisung, die auf Unit und nicht auf den zugewiesenen Wert auswertet?


Was ist die Motivation für eine Scala-Zuweisung, die auf Unit und nicht auf den zugewiesenen Wert auswertet?

Ein gängiges Muster in der I / O-Programmierung ist, Dinge wie folgt zu tun:

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

Aber das ist in Scala nicht möglich, weil ...

bytesRead = in.read(buffer)

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

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


76
2018-01-04 10:37


Ursprung


Antworten:


Ich befürwortete, dass Aufgaben den zugewiesenen Wert und nicht die Einheit zurückgeben. Martin und ich gingen darauf hin und her, aber sein Argument war, dass es eine Verschwendung von Byte-Codes war und sich negativ auf die Leistung auswirkte, wenn man einen Wert auf den Stapel setzte, um ihn 95% der Zeit auszufüllen.


75
2018-01-04 16:18



Ich bin nicht in Insiderinformationen über die tatsächlichen Gründe eingeweiht, aber mein Verdacht ist sehr einfach. Scala macht seitenwirksame Schleifen umständlich zu verwenden, so dass Programmierer natürlich Vorüberlegungen bevorzugen.

Es tut dies in vielerlei Hinsicht. Zum Beispiel haben Sie keine for Schleife, in der Sie eine Variable deklarieren und mutieren. Sie können den Zustand auf einem nicht (einfach) mutieren while Schleife gleichzeitig testen Sie die Bedingung, was bedeutet, dass Sie oft die Mutation kurz davor und am Ende wiederholen müssen. Variablen, die innerhalb von a deklariert sind while Block sind nicht sichtbar von der while Testbedingung, die macht do { ... } while (...) viel weniger nützlich. Und so weiter.

Problemumgehung:

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

Für was auch immer es sich lohnt.

Als eine alternative Erklärung musste Martin Odersky vielleicht ein paar sehr hässliche Käfer sehen, die aus einer solchen Verwendung stammten, und beschloss, es aus seiner Sprache zu verbannen.

BEARBEITEN

David Pollack hat antwortete mit einigen tatsächlichen Fakten, die eindeutig durch die Tatsache bestätigt werden, dass Martin Odersky er selbst kommentierte seine Antwort und gab dem von Pollack vorgebrachten Problem der Leistungstheorie Glauben.


17
2018-01-04 11:57



Dies geschah als Teil von Scala mit einem "formal korrekten" System. Formell gesehen ist die Zuordnung eine rein seitenwirksame Aussage und sollte daher zurückkehren Unit. Das hat einige schöne Konsequenzen. beispielsweise:

class MyBean {
  private var internalState: String = _

  def state = internalState

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

Das state_= Methode kehrt zurück Unit (wie für einen Setter erwartet), genau weil die Zuweisung zurückkehrt Unit.

Ich stimme zu, dass für Muster im C-Stil, wie zum Beispiel das Kopieren eines Streams oder Ähnliches, diese bestimmte Designentscheidung ein wenig mühsam sein kann. Es ist jedoch im Allgemeinen relativ unproblematisch und trägt wirklich zur Gesamtkonsistenz des Typsystems bei.


9
2018-01-04 15:14



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

CQS neigt dazu, an der Schnittstelle von OO und funktionalen Programmierstilen beliebt zu sein, da es einen offensichtlichen Unterschied zwischen Objektmethoden erzeugt, die Nebenwirkungen haben oder nicht (d. H., Die das Objekt verändern). Wenn CQS auf Variablenzuweisungen angewendet wird, geht das weiter als gewöhnlich, aber es gilt dieselbe Idee.

Eine kurze Veranschaulichung, warum CQS nützlich ist: Betrachten Sie eine hypothetische hybride F / OO Sprache mit a List Klasse mit Methoden Sort, Append, First, und Length. Im imperativen OO-Stil könnte man eine Funktion wie diese schreiben wollen:

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()

Im funktionaleren Stil würde man eher so etwas schreiben:

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 zu sein versuchen das gleiche zu tun, aber offensichtlich ist einer der beiden falsch, und ohne mehr über das Verhalten der Methoden zu wissen, können wir nicht sagen, welches.

Mit CQS würden wir jedoch darauf bestehen, dass Append und Sort Um die Liste zu ändern, müssen sie den Unit-Typ zurückgeben, um zu verhindern, dass wir Bugs mit dem zweiten Formular erzeugen, wenn nicht. Das Vorhandensein von Nebenwirkungen wird daher auch in der Methodensignatur implizit.


5
2018-01-04 16:38



Ich denke, das ist, um das Programm / die Sprache frei von Nebenwirkungen zu halten.

Was Sie beschreiben, ist der absichtliche Gebrauch eines Nebeneffektes, der im allgemeinen als schlecht angesehen wird.


4
2018-01-04 10:42



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


4
2018-01-04 11:08



Übrigens: Ich finde den anfänglichen While-Trick blöd, sogar in Java. Warum nicht so etwas?

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

Zugegeben, die Zuweisung erscheint zweimal, aber zumindest ist BytesRead in dem Bereich, zu dem es gehört, und ich spiele nicht mit lustigen Zuweisungstricks ...


2
2018-01-04 21:55



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

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

Dann unter der Einschränkung, die Sie verwenden müssen ref.value Um später auf die Referenz zuzugreifen, können Sie Ihre schreiben while Prädikat als

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 das überprüfen gegen bytesRead implizit, ohne es eingeben zu müssen.


0
2018-06-18 23:03