web-dev-qa-db-ja.com

Scala参照で呼び出すことはできますか?

ScalaはALGOLからの名前による呼び出しをサポートしていることを知っています。それが何を意味するのか理解していると思いますが、Scalaは、次のように参照による呼び出しを行うことができます。 C#、VB.NET、C++で可能ですか?Javaは参照による呼び出しを実行できないことは知っていますが、この制限が言語のみによるものなのか、JVMによるものなのかはわかりません。

これは、巨大なデータ構造をメソッドに渡したいが、そのコピーを作成したくない場合に役立ちます。この場合、参照による呼び出しは完璧に思えます。

20
royco

JavaとScalaはどちらも、値がプリミティブまたはオブジェクトへのポインタのいずれかであることを除いて、値による呼び出しのみを使用します。オブジェクトに可変フィールドが含まれている場合、これには実質的な違いはほとんどありません。参照してください。

オブジェクト自体ではなく、常にオブジェクトへのポインタを渡しているので、巨大なオブジェクトを繰り返しコピーしなければならないという問題はありません。

ちなみに、Scalaの名前による呼び出しは、値による呼び出しを使用して実装されます。値は、式の結果を返す(ポインター)関数オブジェクトです。

44
Rex Kerr

「すべてがオブジェクトである」言語で、オブジェクト参照にアクセスできない場合。 JavaおよびScalaの場合、すべての関数パラメーターは、言語より下の抽象化レベルで値によって渡される参照です。ただし、言語抽象化のセマンティクスの観点からは、次のいずれかがあります。関数に参照オブジェクトのコピーが提供されているかどうかに応じて、参照による呼び出しまたは値による呼び出し。この場合、共有による呼び出しという用語には、参照による呼び出しと値による呼び出しの両方が含まれます。抽象化の言語レベルで。したがって、Javaは、言語セマンティクスより下の抽象化レベルでの値による呼び出しです(つまり、仮想的にどのように変換されるかと比較して) Cまたは仮想マシンのバイトコード)、JavaおよびScalaは(組み込み型を除く))セマンティクスでの参照による呼び出しであるとも言いますその「すべてがオブジェクトである」抽象化の。

JavaおよびScalaでは、特定の組み込み(a/k/aプリミティブ)型は自動的に値渡し(intまたはIntなど)を取得し、すべてのユーザー定義型は参照渡しされます(つまり、値のみを渡すには、手動でコピーする必要があります)。

これをより明確にするために、ウィキペディアの 共有による呼び出しセクション を更新したことに注意してください。

おそらくウィキペディアは、値渡しと値呼び出しの違いについて混乱していますか?値渡しは、関数適用だけでなく代入式にも適用されるため、より一般的な用語だと思いました。私はわざわざウィキペディアでその修正をしようとはしませんでした。他の人がハッシュアウトできるように残しておいてください。

オブジェクトが不変である場合、参照による呼び出しと値による呼び出しの間に「すべてがオブジェクトである」というセマンティクスのレベルに違いはありません。したがって、値による呼び出しと参照による呼び出しの宣言を可能にする言語(私が開発しているScalaのような言語など)は、オブジェクトが変更されるまで値によるコピーを遅らせることによって最適化できます。


これに反対票を投じた人々は、「共有による呼びかけ」が何であるかを理解していないようです。

以下に、Copute言語(JVMを対象とする)に対して行った記述を追加します。ここでは、評価戦略について説明します。


純粋であっても、評価戦略を選択する必要があるため、チューリング完全言語(つまり再帰を許可する)は完全に宣言型ではありません。評価戦略は、関数とその引数の間の相対的な実行時評価順序です。関数の評価戦略は、厳密または非厳密にすることができます。これは、すべての式が関数であるため、それぞれeagerまたはlazyと同じです。熱心とは、引数式が関数の前に評価されることを意味します。一方、lazyは、引数式が関数で最初に使用された実行時にのみ(1回)評価されることを意味します。評価戦略は、パフォーマンス、決定論、デバッグ、および操作的意味論のトレードオフを決定します。純粋なプログラムの場合、表示的意味論の結果は変更されません。純粋な場合、評価順序の命令型の副作用は、メモリ消費、実行時間、待機時間、および非終了ドメインでのみ不確定性を引き起こすためです(つまり、カテゴリに制限されます)。 。

基本的に、すべての式は関数(の合成)です。つまり、定数は入力のない純粋関数、単項演算子は1つの入力のある純粋関数、2項演算子は2つの入力のある純粋関数、コンストラクターは関数、さらには制御ステートメントです(たとえば、 while)は関数でモデル化できます。これらの関数を評価する順序は、構文によって定義されていません。 f(g())は、gの結果に対してgを熱心に評価してからfを評価するか、fを評価して、f内で結果が必要な場合にのみgを遅延評価することができます。

前者(熱心)は値による呼び出し(CBV)であり、後者(怠惰)は名前による呼び出し(CBN)です。 CBVには、Java、Python、Rubyなどの最新のOOP言語で普及している、共有による呼び出しのバリアントがあります。この言語では、不純な関数が参照によっていくつかの可変オブジェクトを暗黙的に入力します。CBNバリアントのcall-by-need(CBNも)があり、関数の引数は1回だけ評価されます(これは関数のメモ化とは異なります)。ほとんどの場合、call-by-needはcall-by-nameの代わりに使用されます。宣言された関数階層と実行時の評価順序が不一致であるため、通常、CBNの両方のバリアントは純粋にしか表示されません。

言語には通常、デフォルトの評価戦略があり、一部の言語には、オプションで関数をデフォルト以外で評価するように強制する構文があります。デフォルトで熱心な言語は、通常、ブール論理積(a/k/a "and"、&&)および論理和(a/k/a "or"、||)演算子を怠惰に評価します。これは、2番目のオペランドが必要ないためです。半分の場合、つまり真||何でも==真と偽&&何でも==偽。

1

Scalaで参照パラメーターをエミュレートする方法は次のとおりです。

def someFunc( var_par_x : Function[Int,Unit] ) {
    var_par_x( 42 ) // set the reference parameter to 42
}

var y = 0 
someFunc( (x => y=x) )
println(y)

そうですね、PascalやC++プログラマーが慣れているものとは異なります。しかし、Scalaはほとんどありません。利点は、呼び出し元がパラメーターに送信された値で実行できることをより柔軟に行えることです。例:.

someFunc( (x => println(x) ) )
0