7

関数の動作を説明する際に、Scala でこの文に出くわしました。

プログラムの操作は、データをその場で変更するのではなく、値の入力を出力値にマップする必要があります

誰かが良い例でそれを説明できますか?

編集: 上記の文をその文脈で説明または例を挙げてください。混乱を招くために複雑にしないでください

4

4 に答える 4

14

これが言及している最も明白なパターンは、Scala と比較した場合に、Java でコレクションを使用するコードの書き方の違いです。Java のイディオムでscala を書いていた場合、その場でデータを変更することによってコレクションを操作することになります。同じことを行う慣用的な scala コードは、入力値を出力値にマッピングすることを好みます。

コレクションに対して実行したいことをいくつか見てみましょう。

フィルタリング

Java で、Deutsche BankList<Trade>で実行された取引のみに関心がある場合、次のようにします。

for (Iterator<Trade> it = trades.iterator(); it.hasNext();) {
    Trade t = it.next();
    if (t.getCounterparty() != DEUTSCHE_BANK) it.remove(); // MUTATION
}

このループの後、私のtradesコレクションには関連する取引のみが含まれます。しかし、私はミューテーションを使用してこれを達成しまし- 不注意なプログラマーは、入力パラメーター、インスタンス変数、またはメソッドの他の場所で使用されていることを簡単に見逃す可能性があります。tradesそのため、彼らのコードが壊れている可能性は十分にあります。さらに、このようなコードは、これと同じ理由でリファクタリングに対して非常に脆弱です。コードの一部をリファクタリングしたいプログラマーは、変更されたコレクションが意図された使用範囲から逸脱しないように細心の注意を払う必要があります。変異したものを使用しています。

Scala と比較します。

val db = trades filter (_.counterparty == DeutscheBank) //MAPPING INPUT TO OUTPUT

これにより、新しいコレクションが作成されます。見ている人には影響せず、trades本質的に安全です。

マッピング

を持っていて、これまで取引してきたユニークな株のList<Trade>を取得したいとします。Set<Stock>繰り返しになりますが、Java のイディオムは、コレクションを作成してそれを変更することです。

Set<Stock> stocks = new HashSet<Stock>();
for (Trade t : trades) stocks.add(t.getStock()); //MUTATION

scala を使用する正しいことは、入力コレクションをマップしてからセットに変換することです。

val stocks = (trades map (_.stock)).toSet  //MAPPING INPUT TO OUTPUT

または、パフォーマンスが気になる場合:

(trades.view map (_.stock)).toSet
(trades.iterator map (_.stock)).toSet

ここでの利点は何ですか?良い:

  1. 私のコードは、部分的に構築された結果を決して観察できません
  2. A => BaColl[A]を取得するためのa への関数の適用は、Coll[B]より明確です。

蓄積中

繰り返しますが、Java ではイディオムは突然変異でなければなりません。行った取引の小数数量を合計しようとしているとします。

BigDecimal sum = BigDecimal.ZERO
for (Trade t : trades) {
    sum.add(t.getQuantity()); //MUTATION
}

繰り返しますが、部分的に構築された結果を誤って観察しないように十分に注意する必要があります! scala では、これを 1 つの式で行うことができます。

val sum = (0 /: trades)(_ + _.quantity) //MAPPING INTO TO OUTPUT

または、他のさまざまな形式:

(trades.foldLeft(0)(_ + _.quantity)
(trades.iterator map (_.quantity)).sum
(trades.view map (_.quantity)).sum

ところで、Java の実装にはバグがあります。 あなたはそれを見つけましたか?

于 2012-05-28T07:02:50.217 に答える
2

私はそれが次の違いだと思います:

var counter = 0
def updateCounter(toAdd: Int): Unit = {
  counter += toAdd
}
updateCounter(8)
println(counter)

と:

val originalValue = 0
def addToValue(value: Int, toAdd: Int): Int = value + toAdd
val firstNewResult = addToValue(originalValue, 8)
println(firstNewResult)

これは大雑把な単純化ですが、より完全な例としては、自分で大変な作業を行うのではなく、foldLeft を使用して結果を構築するようなものがあります

于 2012-05-27T22:02:56.050 に答える
1

つまり、このように純粋な関数を作成すると、常に同じ入力から同じ出力が得られ、副作用がないため、プログラムについて簡単に推論し、それらが正しいことを確認できます。

たとえば、関数:

def times2(x:Int) = x*2

純粋ですが、

def add5ToList(xs: MutableList[Int]) {
    xs += 5
}

副作用としてデータをその場で編集するため、不純です。同じリストがプログラムの他の場所で使用されている可能性があり、変更されたため動作を保証できないため、これは問題です。

純粋なバージョンは不変リストを使用し、新しいリストを返します

def add5ToList(xs: List[Int]) = {
    5::xs
}
于 2012-05-28T08:28:30.167 に答える
1

コレクションにはたくさんの例があり、簡単に手に入れることができますが、間違った印象を与える可能性があります. この概念は、言語のすべてのレベルで機能します (ただし、VM レベルでは機能しません)。その一例がケースクラスです。次の 2 つの代替案を検討してください。

// Java-style
class Person(initialName: String, initialAge: Int) {
    def this(initialName: String) = this(initialName, 0)
    private var name = initialName
    private var age = initialAge
    def getName = name
    def getAge = age
    def setName(newName: String) { name = newName }
    def setAge(newAge: Int) { age = newAge }
}

val employee = new Person("John")
employee.setAge(40) // we changed the object

// Scala-style
case class Person(name: String, age: Int) {
  def this(name: String) = this(name, 0)
}
val employee = new Person("John")
val employeeWithAge = employee.copy(age = 40) // employee still exists!

この概念は、不変コレクション自体の構築に適用されます。 aListは変更されません。代わりに、必要に応じて新しいListオブジェクトが作成されます。永続的なデータ構造を使用すると、変更可能なデータ構造で発生するコピーが削減されます。

于 2012-05-28T16:35:19.673 に答える