3

多くのWeb記事では、関数型プログラミングは、あらゆる種類の変数の再割り当てを回避し、少なくとも「最終的な」変数のみを促進して、特に読みやすくするものとして提示されています。

それらのほとんどは、カウンター変数が増分する貧弱なループのサンプルです。(有名なi++またはのようにx = x + 1。ここにそれを説明するボブおじさんの記事:FPエピソード1

したがって、これらの記事は、可変変数を頼りにすると副作用が発生することが多く、特に「参照透過性」と呼ばれるものを防ぎ、マルチスレッドまたはより優れたマルチプロセッサで実行されるプログラムの構築を困難にすることを示しています。

私の質問は次のとおりです。ご存知のとおり、i++一般的にスレッドLOCAL変数であるため、並行処理を行っても問題は発生しません。

割り当ての欠点としてローカル変数を使用したループのような例を選択し、並行性プログラミングが危険であると直接結論付けることを許可するのはなぜですか?これらは両方とも私とはまったく関係がありません。

より明確にするために、enemyJavaのようにロックのすべての定型文を使いすぎずに、明らかに並行プログラミングのグローバル変数(またはフィールドオブジェクト)の再割り当てを選択してください。

このループサンプルは、関数型プログラミングの利点を命令型プログラマーに伝えるための最良の例ではないと本当に思います。

Listさらに、たとえばScalaは.scalaクラスのように多くのwhileループパターンを使用するため、「noob」関数型プログラマーとの混乱を招きます。

override def take(n: Int): List[A] = {
    val b = new ListBuffer[A]
    var i = 0
    var these = this
    while (!these.isEmpty && i < n) {  
      i += 1   // reassignment here
      b += these.head
      these = these.tail
    }
    if (these.isEmpty) this
    else b.toList
  } 
4

2 に答える 2

7

Odersky自身が、APIが機能することを目指していると言ったと思いますが、特定の実装には内部コードが最適です。したがって、Scalaライブラリの内部で「Scalaの有効活用」や「FPの優れた例」を検索するべきではないでしょう。

たとえば、可変状態を使用してインデックスを保持することも、実際にはエラーが発生しやすくなります。したがって、コレクション全体(filter / map / flatMapなど)に対して操作を使用することを目的とする必要があります。これにより、「範囲外のインデックス」などについて心配する必要がなくなります。それでも、これらの操作により、多くの一時的/中間コレクションが作成されることが多く、そのため、余分なガベージコレクションが発生します。これは通常、99%のプログラムでは問題になりませんが、繰り返しになりますが、これらはScalaライブラリ内部で可能な限り最適化されています。

そうです、すべての場所がありますが、バグの可能性のある場所が少なく、テストが容易で、読みやすさが向上しているため、変更可能な状態をできるだけ少なくして「生き残る」ことを実践することは、シングルスレッドプログラムにとっても良い習慣です。

于 2012-12-24T13:16:05.923 に答える
6

単純なループでは、問題はありません。並行性の問題になることはなくおそらく変数を追跡できます。まあ、多分。

// Take the first n items that pass p
def takeGood(n: Int)(p: A => Boolean): List[A] = {
  val b = new ListBuffer[A]
  var these = this
  var i = 0
  while (!these.isEmpty && i < n) {
    i += 1
    if (p(these.head)) b += these.head
    these = these.tail
  }
  b.toList
}

ええと、これが機能しないことを除いて、私たちは、取るループだけでなく、すべてのiループでインクリメントしました。

再帰を使用すると、少なくとも、何をしているのかがより明確になります。

def takeGood[A](these: List[A], n: Int)(p: A => Boolean)(b: ListBuffer[A] = new ListBuffer[A]): List[A] = {
  if (these.isEmpty || n <= 0) b.toList
  else if (p(these.head)) takeGood(these.tail, n-1)(p)({ b += these.head; b })
  else takeGood(these.tail, n)(p)(b)
}

したがって、並行性が機能していない場合でも機能スタイルを使用することには利点があります。場合によっては(特にループの場合)、ループがはるかに明確になり、エラーの可能性が低くなります。

並行性は追加の利点をもたらします。一貫性はありますが、通常、一貫性がない、またはデッドロックするよりも古くなっている方がはるかに優れているためです。しかし、それはイテレータを使用したwhileループに表示されるものではありません。

于 2012-12-24T13:25:44.867 に答える