関数型プログラミング-不変性は高価ですか?[閉まっている]


98

問題は2つの部分に分かれています。最初は概念的なものです。次に、同じ質問をScalaでより具体的に見ていきます。

  1. プログラミング言語で不変のデータ構造のみを使用すると、実際には特定のアルゴリズム/ロジックの実装が本質的に計算コストが高くなりますか?これは、不変性が純粋に関数型言語の中心的な信条であることを示しています。これに影響を与える他の要因はありますか?
  2. より具体的な例を見てみましょう。クイックソートは、通常、メモリ内データ構造に対する可変操作を使用して教えられ、実装されます。可変バージョンに匹敵する計算およびストレージオーバーヘッドを備えたPURE機能的な方法で、そのようなことをどのように実装しますか。特にScalaで。以下にいくつかの大まかなベンチマークを含めました。

詳細:

私は命令型プログラミングのバックグラウンド(C ++、Java)から来ています。私は関数型プログラミング、特にScalaを調査しています。

純粋な関数型プログラミングの主要な原則のいくつか:

  1. 機能は一流の市民です。
  2. 関数には副作用がないため、オブジェクト/データ構造は不変です。

最近のJVMはオブジェクト作成に関して非常に効率的であり、ガベージコレクションは存続期間の短いオブジェクトに対して非常に安価ですが、オブジェクトの作成を最小限に抑える方が適切でしょう。少なくとも、同時実行性とロックが問題にならないシングルスレッドアプリケーションでは。Scalaはハイブリッドパラダイムであるため、必要に応じて、可変オブジェクトを使用して命令型コードを記述することを選択できます。しかし、オブジェクトを再利用し、割り当てを最小限にしようと何年も費やした人として。それさえ許さない思想の学校をよく理解して欲しい。

特定のケースとして、このチュートリアル 6のこのコードスニペットに少し驚いた。これには、Java版のQuicksortがあり、その後に、見栄えの良いScalaの実装が続きます。

実装をベンチマークする私の試みはここにあります。詳細なプロファイリングは行っていません。しかし、私の推測では、割り当てられるオブジェクトの数は線形(再帰呼び出しごとに1つ)であるため、Scalaバージョンの方が遅くなります。テールコールの最適化が機能する可能性はありますか?私が正しい場合、Scalaは自己再帰呼び出しの末尾呼び出しの最適化をサポートしています。だから、それはそれを助けるだけであるべきです。Scala 2.8を使用しています。

Javaバージョン

public class QuickSortJ {

    public static void sort(int[] xs) {
      sort(xs, 0, xs.length -1 );
    }

    static void sort(int[] xs, int l, int r) {
      if (r >= l) return;
      int pivot = xs[l];
      int a = l; int b = r;
      while (a <= b){
        while (xs[a] <= pivot) a++;
        while (xs[b] > pivot) b--;
        if (a < b) swap(xs, a, b);
      }
      sort(xs, l, b);
      sort(xs, a, r);
    }

    static void swap(int[] arr, int i, int j) {
      int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
    }
}

Scalaバージョン

object QuickSortS {

  def sort(xs: Array[Int]): Array[Int] =
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length / 2)
      Array.concat(
        sort(xs filter (pivot >)),
        xs filter (pivot ==),
        sort(xs filter (pivot <)))
    }
}

実装を比較するScalaコード

import java.util.Date
import scala.testing.Benchmark

class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {

  val ints = new Array[Int](100000);

  override def prefix = name
  override def setUp = {
    val ran = new java.util.Random(5);
    for (i <- 0 to ints.length - 1)
      ints(i) = ran.nextInt();
  }
  override def run = sortfn(ints)
}

val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut   = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java   " )

benchImmut.main( Array("5"))
benchMut.main( Array("5"))

結果

5回の連続実行の時間(ミリ秒)

Immutable/Functional/Scala    467    178    184    187    183
Mutable/Imperative/Java        51     14     12     12     12

10
単純に実装したり、命令型言語用に拡張した方法で実装したりすると、コストがかかります。スマートコンパイラー(例:GHC、Haskellコンパイラー、およびHaskellには不変の値しかない、不変性と、可変性を使用してコードに匹敵する達成可能なパフォーマンスを利用できます。言うまでもなく、quicksortの素朴な実装は、特に重い再帰とO(n)リスト連結を使用するため、非常に低速です。ただし、疑似コードバージョンより短いです;)

3
関連するすばらしいブログ記事がここにあります:blogs.sun.com/jrose/entry/larval_objects_in_the_vm は彼を励まします。これはJavaと機能的なVM言語に大きな利益をもたらすからです
andersoj

2
このSOスレッドには、関数型プログラミングの効率性に関する多くの詳細な議論があります。stackoverflow.com/questions/1990464/…。私が知りたかったことの多くに答えます。
smartnut007 2010年

5
ここで最も素朴なのはベンチマークです。そのようなコードでは何もベンチマークできません!結論を出す前に、JVMでベンチマークを行うことに関するいくつかの記事を真剣に読む必要があります... JVMを実行する前に、JVMがまだコードをJIT処理していない可能性があることをご存知ですか?ヒープの初期値と最大サイズを適切に設定しましたか(JVMプロセスがより多くのメモリを要求する時間を考慮しないように)?コンパイルまたは再コンパイルされているメソッドを知っていますか?GCを知っていますか?このコードから得られる結果は、まったく何の意味もありません!
Bruno Reis

2
@userunknownいいえ、それは宣言的です。命令型プログラミングは「コマンドで状態を変更する」のに対し、関数型プログラミングは「状態の変更を回避する」宣言型プログラミングのパラダイムです(Wikipedia)。したがって、はい、機能と命令は2つのまったく異なるものであり、作成したコード命令ではありません
ブライアンマカッション

回答:


105

このあたりで誤解されている点がいくつかあるので、いくつかの点を明確にしたいと思います。

  • 「インプレース」クイックソートは実際にはインプレースではありません(クイックソートは定義上インプレースではありません)。再帰的なステップにはスタックスペースの形式で追加のストレージが必要です。これは、最良の場合はO(log n)のオーダーですが、最悪の場合はOn)のオーダーです。

  • 配列を操作するクイックソートの機能バリアントを実装すると、目的が達成されません。配列は不変ではありません。

  • クイックソートの「適切な」機能実装は不変のリストを使用します。もちろん、インプレースではありませんが、手続き型のインプレースバージョンと同じ最悪の漸近ランタイム(On ^ 2))とスペースの複雑さ(On))があります。

    平均して、その実行時間がある、まだ(インプレースバリアントのそれと同等のONログN))。ただし、そのスペースの複雑さはOn)のままです。


あり2つの明らかに不利機能クイックソートの実装は。以下では、Haskellの紹介にあるHaskellでのこのリファレンス実装(Scalaは知りませんが…)について考えてみましょう。

qsort []     = []
qsort (x:xs) = qsort lesser ++ [x] ++ qsort greater
    where lesser  = (filter (< x) xs)
          greater = (filter (>= x) xs)
  1. 第1の欠点は非常に柔軟性に欠けるピボット要素の選択です。最新のクイックソート実装の長所は、ピボットの賢い選択に大きく依存しています(Bentley らによる「ソート関数のエンジニアリング」と比較してください)。上記のアルゴリズムはその点で貧弱であり、平均パフォーマンスを大幅に低下させます。

  2. 第二に、このアルゴリズムはOn)演算である(リスト構築の代わりに)リスト連結を使用します。これは漸近的な複雑さには影響しませんが、測定可能な要因です。

3番目の欠点はやや隠されています。「インプレース」バリアントとは異なり、この実装はヒープのリストにコンスセルのメモリを継続的に要求し、メモリをあちこちに分散させる可能性があります。その結果、このアルゴリズムのキャッシュの局所性は非常に低くなります。最近の関数型プログラミング言語のスマートアロケーターがこれを軽減できるかどうかはわかりませんが、最近のマシンでは、キャッシュミスがパフォーマンスを大幅に低下させています。


結論は何ですか?他とは異なり、クイックソートが本質的に必須であるとは言えません。そのため、FP環境でのパフォーマンスは良くありません。それどころか、クイックソートは関数型アルゴリズムの完璧な例であると私は主張します。それはシームレスに不変の環境に変換され、その漸近的な実行時間と空間の複雑さは手続き型実装と同等であり、その手続き型実装でさえ再帰を採用しています。

ただし、このアルゴリズムは、不変のドメインに制限されている場合でもパフォーマンスが低下します。この理由は、アルゴリズムには、配列でのみ効率的に実行できる多くの(場合によっては低レベルの)微調整から利益を得るという独特の特性があるためです。クイックソートの素朴な説明では、これらすべての複雑な機能(機能と手順の両方のバリエーション)がありません。

「ソート関数のエンジニアリング」を読んだ後は、クイックソートをエレガントなアルゴリズムと見なすことはできません。効率的に実装された、それは不格好な混乱であり、エンジニアの作品ではなく、アーティストの作品ではありません(エンジニアリングの価値を下げるためではありません!これには独自の美しさがあります)。


しかし、この点はクイックソートに特有のものであることも指摘したいと思います。すべてのアルゴリズムが同じ種類の低レベルの調整に適しているわけではありません。多くのアルゴリズムとデータ構造は、不変の環境でパフォーマンスを低下させることなく実際に表現できます。

そして不変性にもすることができます減少コストのかかるコピーまたはクロススレッドの同期の必要性を除去することにより、パフォーマンスコストを。

したがって、元の質問に答えるために、「不変性は高価ですか?」–クイックソートの特定のケースでは、実際に不変性の結果であるコストが発生します。しかし、一般的にはありません


10
+1-正解です。私は個人的になったことでしょう。けれども、時々ではなくなし。それでも、それは単なる性格です。あなたは問題を非常によく説明しました。
レックスカー

6
不変値を使用した適切な実装は、命令型バージョンとは対照的に、すぐに並列化できることを追加する必要があります。現代の技術の文脈では、これはますます重要になります。
Raphael

qsort lesser ++ (x : qsort greater)ヘルプをどの程度使用しますか?
ソロモンウッコ

42

関数型プログラミングのベンチマークとして、これには多くの問題点があります。ハイライトは次のとおりです。

  • ボックス化/ボックス化解除が必要な場合があるプリミティブを使用しています。プリミティブオブジェクトのラップのオーバーヘッドをテストするのではなく、不変性をテストしようとしています。
  • インプレース操作が非常に効果的である(そしておそらくそうである)アルゴリズムを選択しました。可変的に実装した方が高速なアルゴリズムが存在することを示すには、これが適切な選択肢です。そうでなければ、これは誤解を招く可能性があります。
  • 間違ったタイミング機能を使用しています。を使用しSystem.nanoTimeます。
  • ベンチマークが短すぎるため、JITコンパイルが測定時間の重要な部分ではないことを確信できません。
  • 配列は効率的な方法で分割されていません。
  • 配列は可変であるため、FPでそれらを使用することはとにかく奇妙な比較です。

したがって、この比較は、高性能のコードを作成するために言語(およびアルゴリズム)を詳細に理解する必要があることを示す優れた例です。しかし、FPと非FPの比較としてはあまり良いものではありません。必要な場合は、コンピュータ言語ベンチマークゲームでHaskellとC ++を比較してください。そこに持ち帰るメッセージは、ペナルティは通常2倍または3倍程度であるということですが、それは実際に依存します。(Haskellの人々が可能な限り最速のアルゴリズムを作成したという約束はありませんが、少なくともそれらのいくつかはおそらく試したでしょう!そして、HaskellのいくつかはCライブラリを呼び出します...)

ここで、Quicksortのより妥当なベンチマークが必要であり、これがおそらくFPアルゴリズムと可変アルゴリズムの最悪のケースの1つであることを認識し、データ構造の問題を無視する(つまり、不変の配列を持つ可能性があると偽る)とします。

object QSortExample {
  // Imperative mutable quicksort
  def swap(xs: Array[String])(a: Int, b: Int) {
    val t = xs(a); xs(a) = xs(b); xs(b) = t
  }
  def muQSort(xs: Array[String])(l: Int = 0, r: Int = xs.length-1) {
    val pivot = xs((l+r)/2)
    var a = l
    var b = r
    while (a <= b) {
      while (xs(a) < pivot) a += 1
      while (xs(b) > pivot) b -= 1
      if (a <= b) {
        swap(xs)(a,b)
        a += 1
        b -= 1
      }
    }
    if (l<b) muQSort(xs)(l, b)
    if (b<r) muQSort(xs)(a, r)
  }

  // Functional quicksort
  def fpSort(xs: Array[String]): Array[String] = {
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length/2)
      val (small,big) = xs.partition(_ < pivot)
      if (small.length == 0) {
        val (bigger,same) = big.partition(_ > pivot)
        same ++ fpSort(bigger)
      }
      else fpSort(small) ++ fpSort(big)
    }
  }

  // Utility function to repeat something n times
  def repeat[A](n: Int, f: => A): A = {
    if (n <= 1) f else { f; repeat(n-1,f) }
  }

  // This runs the benchmark
  def bench(n: Int, xs: Array[String], silent: Boolean = false) {
    // Utility to report how long something took
    def ptime[A](f: => A) = {
      val t0 = System.nanoTime
      val ans = f
      if (!silent) printf("elapsed: %.3f sec\n",(System.nanoTime-t0)*1e-9)
      ans
    }

    if (!silent) print("Scala builtin ")
    ptime { repeat(n, {
      val ys = xs.clone
      ys.sorted
    }) }
    if (!silent) print("Mutable ")
    ptime { repeat(n, {
      val ys = xs.clone
      muQSort(ys)()
      ys
    }) }
    if (!silent) print("Immutable ")
    ptime { repeat(n, {
      fpSort(xs)
    }) }
  }

  def main(args: Array[String]) {
    val letters = (1 to 500000).map(_ => scala.util.Random.nextPrintableChar)
    val unsorted = letters.grouped(5).map(_.mkString).toList.toArray

    repeat(3,bench(1,unsorted,silent=true))  // Warmup
    repeat(3,bench(10,unsorted))     // Actual benchmark
  }
}

機能するクイックソートの変更に注意してください。可能な限りデータを1度だけ通過し、組み込みのソートと比較されます。実行すると、次のようになります。

Scala builtin elapsed: 0.349 sec
Mutable elapsed: 0.445 sec
Immutable elapsed: 1.373 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.441 sec
Immutable elapsed: 1.374 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.442 sec
Immutable elapsed: 1.383 sec

したがって、独自のソートを書こうとするのは悪い考えであることを知るほかに、不変のクイックソートがいくらか注意深く実装されている場合、不変のクイックソートには3倍のペナルティがあることがわかります。(3つの配列を返すtrisectメソッドを作成することもできます。これらは、ピボットより小さい、等しい、ピボットより大きいものです。これにより、処理速度が少し速くなる場合があります。)


ちょうどボクシング/アンボクシングに関して。何かこれはJava側のペナルティでなければなりませんか?Isnt IntはScala(vs Integer)の推奨数値タイプです。したがって、スカラー側でボクシングハッピングはありません。ボクシングはjava側でのみ問題となります。オートボクシングはjava.lang.Integer / intへのscala Intを形成するためです。これは、このテーマについて詳細に説明しているリンクですansorg-it.com/en/scalanews-001.html
smartnut007

はい、私はここで悪魔の擁護者を演じています。可変性は、クイックソート設計の不可欠な部分です。そのため、問題に対する純粋な機能的アプローチに非常に興味がありました。ため息、私はこの声明をスレッドで10回目に言った:-)。目が覚めて戻ったときに、残りの投稿を確認します。ありがとう。
smartnut007 2010年

2
@ smartnut007-Scalaコレクションは汎用です。ジェネリックスでは、ほとんどの場合、ボックス化された型が必要です(特定のプリミティブ型に特化するための取り組みが進行中ですが)。そのため、気の利いたコレクションのメソッドをすべて使用することはできず、プリミティブ型のコレクションを渡すときにペナルティがないと仮定することができます。プリミティブ型は、途中でボックス化し、途中でボックス化解除する必要がある可能性が高いです。
Rex Kerr、

私があなたが述べた一番の欠陥が単なる推測であるという事実が好きではありません:-)
smartnut007 '06 / 11/10

1
@ smartnut007-確認するのが難しいため、これは一番の問題です。trueの場合、結果が本当に台無しになります。ボクシングがないと確信している場合は、その欠陥は有効ではないことに同意します。問題は、ボクシングが存在することではなく、ボクシングが存在するかどうかがわからないことです(私もどちらかわかりません -専門化により、これを理解するのが難しくなりました)。Java側(またはScala可変実装)では、プリミティブのみを使用するため、ボクシングはありません。とにかく、不変バージョンはn log nのスペースで機能するため、実際に比較/スワップのコストとメモリ割り当てを比較することになります。
Rex Kerr、

10

あなたがを使用してArray.concatいるので、私はScalaバージョンが実際に末尾再帰であるとは思いません。

また、これが慣用的なScalaコードであるからといって、これが最善の方法であるとは限りません。

これを行う最良の方法は、Scalaの組み込みソート関数の1つを使用することです。そうすれば、不変性が保証され、高速なアルゴリズムを使用していることがわかります。

Stack Overflowの質問を参照してください。Scalaで配列を並べ替える方法を教えてください。例として。


4
また、私はあなたが2つの再帰呼び出し作るために持っているように尾の再帰は、クイックソート可能があるとは思わない
アレックス・ロー

1
可能性があります。継続クロージャーを使用して、スタックになりそうなフレームをヒープに持ち上げるだけです。
ブライアン

組み込みのscala.util.Sorting.quickSort(array)は配列を変更します。それはJavaと同じくらい速く、驚くべきことではありません。効率的な純粋機能ソリューションに興味があります。そうでない場合、その理由。Scalaの制限ですか、それとも一般的な機能パラダイムですか。そのちょっとしたこと。
smartnut007 2010年

@ smartnut007:使用しているScalaのバージョンは?Scala 2.8ではarray.sorted、元の配列を変更せずに、ソートされた新しい配列を返すことができます。
missingfaktor

@AlexLo-末尾再帰的なクイックソートが可能です。次のようなものTAIL-RECURSIVE-QUICKSORT(Array A, int lo, int hi): while p < r: q = PARTITION(A, lo, hi); TAIL-RECURSIVE-QUICKSORT(A, lo, q - 1); p = q + 1;
Jakeway

8

不変性は高価ではありません。プログラムが実行する必要があるタスクの小さなサブセットを測定し、クイックソートの測定など、起動する可変性に基づいてソリューションを選択する場合は、確かにコストがかかる可能性があります。

簡単に言えば、純粋に関数型の言語を使用している場合は、クイックソートは行いません。

これを別の角度から考えてみましょう。次の2つの関数について考えてみましょう。

// Version using mutable data structures
def tailFrom[T : ClassManifest](arr: Array[T], p: T => Boolean): Array[T] = {
  def posIndex(i: Int): Int = {
    if (i < arr.length) {
      if (p(arr(i)))
        i
      else
        posIndex(i + 1)
    } else {
      -1
    }
  }

  var index = posIndex(0)

  if (index < 0) Array.empty
  else {
    var result = new Array[T](arr.length - index)
    Array.copy(arr, index, result, 0, arr.length - index)
    result
  }
}

// Immutable data structure:

def tailFrom[T](list: List[T], p: T => Boolean): List[T] = {
  def recurse(sublist: List[T]): List[T] = {
    if (sublist.isEmpty) sublist
    else if (p(sublist.head)) sublist
    else recurse(sublist.tail)
  }
  recurse(list)
}

それをベンチマークすると、変更可能なデータ構造を使用するコードは、配列をコピーする必要があるため、パフォーマンスが大幅に低下することがわかります。一方、不変のコードはそれ自体に関与する必要はありません。

不変のデータ構造を使用してプログラミングする場合は、その長所を利用するようにコードを構造化します。これは、単にデータ型や個々のアルゴリズムではありません。プログラムは別の方法で設計されます。

これが、ベンチマークが通常意味をなさない理由です。いずれかのスタイルに自然なアルゴリズムを選択し、そのスタイルが優先されるか、またはアプリケーション全体のベンチマークを行うかのどちらかであり、これは実際的でないことがよくあります。


7

配列の並べ替えは、宇宙で最も必須のタスクです。多くのエレガントな「不変の」戦略/実装が「配列の並べ替え」マイクロベンチマークでうまく機能しないことは当然のことです。ただし、これは不変性が「一般的に」高価であることを意味するものではありません。不変の実装が変更可能なものと同等に実行される多くのタスクがありますが、配列のソートはそれらの1つではないことがよくあります。


7

命令型アルゴリズムとデータ構造を関数型言語に単純に書き換えるだけの場合、それは確かに高価で役に立たないでしょう。物事を輝かせるには、関数型プログラミングでのみ利用可能な機能を使用する必要があります:データ構造の永続性、遅延評価など。


Scalaでの実装を提供できるほど親切にしていただけませんか。
smartnut007 2010年

3
powells.com/biblio/17-0521631246-0(Chris Okasakiによる純粋に機能的なデータ構造)-この本に目を通してください。効果的なアルゴリズムとデータ構造を実装するときに、関数型プログラミングの利点を活用することについて語るべき強力なストーリーがあります。
Vasil Remeniuk、2011年

1
code.google.com/p/pfds Debashish GhoshによってScalaに実装されたいくつかのデータ構造
Vasil Remeniuk 2010年

Scalaが必須ではないと考える理由を説明していただけますか?list.filter (foo).sort (bar).take (10)-より不可欠なものは何ですか?
ユーザー不明

7

Scalaにおける不変性のコスト

これは、Javaのバージョンとほぼ同じ速度のバージョンです。;)

object QuickSortS {
  def sort(xs: Array[Int]): Array[Int] = {
    val res = new Array[Int](xs.size)
    xs.copyToArray(res)
    (new QuickSortJ).sort(res)
    res
  }
}

このバージョンでは、配列のコピーが作成され、Javaバージョンを使用して配列が並べ替えられ、コピーが返されます。Scalaは内部で不変の構造を使用することを強制しません。

したがって、Scalaの利点は、必要に応じて可変性と不変性を活用できることです。欠点は、それを間違って実行しても、不変性のメリットが実際には得られないことです。


これは質問に対する正確な答えではありませんが、良い答えの一部だと思います。可変構造を使用すると、Quicksortの方が高速です。しかし、不変性の主な利点はインターフェースであり、Scalaでは少なくとも両方を使用できます。クイックソートの方がミュータビリティは速くなりますが、パフォーマンスの高い、ほとんどの場合不変のコードを書く方法にはなりません。
Paul Draper

7

QuickSortは、インプレースで実行した方が高速であることがわかっているため、公平な比較とは言えません。

それを言った... Array.concat?他に何もない場合は、命令型プログラミング用に最適化されたコレクション型が、関数型アルゴリズムで使用しようとすると、特に遅いことを示しています。他のほとんどすべての選択はより速くなります!


考慮すべきもう1つの非常に重要なポイント、おそらく2つのアプローチを比較するとき最も重要な問題は、「これが複数のノード/コアにどれだけうまくスケールアウトできるか」です。

おそらく、不変のクイックソートを探しているのであれば、実際には並列クイックソートが必要なので、そうしています。ウィキペディアには、この主題に関するいくつかの引用があります:http : //en.wikipedia.org/wiki/Quicksort#Parallelizations

scalaバージョンは、関数が再帰する前に単純にforkできるため、十分なコアが利用可能であれば、数十億のエントリを含むリストを非常に迅速にソートできます。

現在、私のシステムのGPUは、Scalaコードを実行するだけで128コアを利用できます。これは、現在の世代から2年遅れたシンプルなデスクトップシステムです。

シングルスレッドの命令型アプローチに対してどのように積み重なるでしょうか...

したがって、おそらくより重要な質問は次のとおりです。

「個々のコアが速くなることはなく、同期化/ロックが並列化の本当の課題を提示しているとすれば、可変性は高価ですか?」


そこには議論はありません。クイックソートは、定義上、メモリ内ソートです。ほとんどの人が大学でそれを覚えていると思います。しかし、どのようにして純粋な機能的な方法でクイックソートを行うのでしょうか。すなわち副作用なし。
smartnut007 2010年

その重要な原因として、機能パラダイムは副作用のある関数と同じくらい速くなる可能性があるという主張があります。
smartnut007 2010年

リスト版は時間を半分に減らします。それでも、Javaバージョンの速度に近いところはありません。
smartnut007 2010年

Scalaが必須ではないと考える理由を説明していただけますか?list.filter (foo).sort (bar).take (10)-より不可欠なものは何ですか?ありがとう。
ユーザー不明

@user unknown:おそらく、あなたは「命令的」が何を意味するのかを明確にすることができます。なぜなら、あなたの述べた例は私にはうまく機能しているように見えるからです。Scala自体は命令型でも宣言型でもありません。言語は両方のスタイルをサポートしており、これらの用語は特定の例を説明するのに最適です。
Kevin Wright、

2

OOプログラミングは抽象化を使用して複雑さを隠し、関数型プログラミングは不変性を使用して複雑さを取り除くと言われています。Scalaのハイブリッドの世界では、OOを使用して命令コードを非表示にし、アプリケーションコードを賢くすることができます。実際、コレクションライブラリは多くの命令コードを使用しますが、それを使用すべきではないという意味ではありません。他の人が言ったように、注意して使用すると、あなたは本当にここで両方の世界の最高のものを手に入れます。


Scalaが必須ではないと考える理由を説明していただけますか?list.filter (foo).sort (bar).take (10)-より不可欠なものは何ですか?ありがとう。
不明なユーザー

Scalaは必須ではないと彼が言ったところはわかりません。
Janx 2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.