web-dev-qa-db-ja.com

Scala割り当てられた値ではなく、Unitに評価される割り当ての動機は何ですか?

Scala割り当てられた値ではなく、Unitに評価される割り当ての動機は何ですか?

I/Oプログラミングの一般的なパターンは、次のようなことです。

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

しかし、これはScalaでは不可能です。

bytesRead = in.read(buffer)

.. bytesReadの新しい値ではなく、Unitを返します。

関数型言語から除外するのは興味深いことのようです。なぜそうなったのかしら?

83
Graham Lea

私は、割り当てが単位ではなく割り当てられた値を返すようにすることを提唱しました。マーティンと私はそれを行ったり来たりしましたが、彼の主張は、95%の時間で値をスタックに置くことはバイトコードの無駄であり、パフォーマンスに悪影響を与えるというものでした。

84
David Pollak

私は実際の理由についての内部情報に精通していませんが、私の疑いは非常に単純です。 Scalaは、副作用のあるループを使いにくくするため、プログラマーは当然のことながら内包表記を好みます。

それは多くの方法でこれを行います。たとえば、変数を宣言して変更するforループはありません。条件のテストと同時にwhileループの状態を(簡単に)変更することはできません。つまり、多くの場合、その直前と最後に変更を繰り返す必要があります。 whileブロック内で宣言された変数は、whileテスト条件からは見えないため、do { ... } while (...)の有用性は大幅に低下します。等々。

回避策:

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

それが価値があるものは何でも。

別の説明として、おそらくマーティン・オーダスキーは、そのような使用法に由来するいくつかの非常に醜いバグに直面しなければならず、彼の言語からそれを非合法化することに決めました。

[〜#〜]編集[〜#〜]

David Pollackanswered いくつかの実際の事実を持っていますが、これは Martin Odersky 自身が彼の答えにコメントし、パフォーマンスに信頼を与えているという事実によって明確に裏付けられていますポラックが提起した関連問題の議論。

20

これは、Scalaより「正式に正しい」型システムを持つことの一部として発生しました。正式に言えば、割り当ては純粋に副作用のあるステートメントであるため、Unitを返す必要があります。いくつかの素晴らしい結果があります;例:

class MyBean {
  private var internalState: String = _

  def state = internalState

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

state_=メソッドは、割り当てがUnitを返すため、(セッターに期待されるように)Unitを返します。

ストリームのコピーなどのCスタイルのパターンの場合、この特定の設計上の決定は少し面倒になる可能性があることに同意します。ただし、実際には一般的に比較的問題はなく、型システムの全体的な一貫性に大きく貢献します。

11
Daniel Spiewak

おそらくこれは コマンドクエリ分離 原則によるものですか?

CQSは、OOと関数型プログラミングスタイルの共通部分で人気があります。これは、副作用のあるオブジェクトメソッドとないオブジェクトメソッド(つまり、オブジェクトを変更するメソッド)を明確に区別するためです。 。変数の割り当てにCQSを適用すると、通常よりもさらに進んでいますが、同じ考え方が当てはまります。

CQSが役立つ理由の簡単な説明:メソッドListSortAppendを含むFirstクラスを持つ架空のハイブリッドF/OO言語について考えてみます。およびLength。命令型OOスタイルでは、次のような関数を記述したい場合があります。

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

一方、より機能的なスタイルでは、次のように書く可能性が高くなります。

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

これらは同じことをしようとしているようですが、明らかに2つのうちの一方が正しくなく、メソッドの動作について詳しく知らなくても、どれかわからない。

ただし、CQSを使用すると、AppendSortがリストを変更する場合、ユニットタイプを返す必要があるため、2番目のフォームを使用してバグが発生するのを防ぐ必要があります。 。したがって、副作用の存在もメソッドシグネチャに暗黙的に含まれます。

7
C. A. McCann

割り当てをブール式として使用するのは最適なスタイルではありません。 2つのことを同時に実行すると、エラーが発生することがよくあります。また、Scalasの制限により、「==」の代わりに「=」を誤って使用することは回避されます。

4
deamon

これは、プログラム/言語に副作用がないようにするためだと思います。

あなたが説明するのは、一般的な場合に悪いことと考えられている副作用の意図的な使用です。

4
Jens Schauder

ちなみに、Javaでも、最初のwhile-trickはばかげています。なぜこのような何かをしないのですか?

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

確かに、割り当ては2回表示されますが、少なくともbytesReadはそれが属するスコープ内にあり、面白い割り当てのトリックで遊んでいません...

2
Landei

間接参照の参照型がある限り、これを回避することができます。ナイーブな実装では、任意のタイプに以下を使用できます。

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

次に、後で参照にアクセスするためにref.valueを使用する必要があるという制約の下で、while述語を次のように記述できます。

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

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

bytesReadに対するチェックは、入力しなくても、より暗黙的な方法で実行できます。

0
Debilski