私が理解しているように、Scalaでは、関数は次のいずれかで呼び出すことができます
- 値渡しまたは
- 名前で
たとえば、次の宣言が与えられた場合、関数がどのように呼び出されるかがわかりますか?
宣言:
def f (x:Int, y:Int) = x;
電話
f (1,2)
f (23+55,5)
f (12+3, 44*11)
ルールを教えてください。
私が理解しているように、Scalaでは、関数は次のいずれかで呼び出すことができます
たとえば、次の宣言が与えられた場合、関数がどのように呼び出されるかがわかりますか?
宣言:
def f (x:Int, y:Int) = x;
電話
f (1,2)
f (23+55,5)
f (12+3, 44*11)
ルールを教えてください。
あなたが示した例は値渡しのみを使用しているため、違いを示す新しい、より単純な例を示します。
まず、副作用のある関数があると仮定しましょう。この関数は何かを出力し、Int
.
def something() = {
println("calling something")
1 // return value
}
ここで、1 つは値による呼び出しスタイル ( ) で引数を取り、もう 1 つは名前による呼び出しスタイル ( )Int
で引数を取ることを除いて、まったく同じ引数を受け入れる 2 つの関数を定義します。x: Int
x: => Int
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
副作用関数でそれらを呼び出すとどうなるでしょうか?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
したがって、値渡しバージョンでは、渡された関数呼び出し ( something()
) の副作用が 1 回だけ発生したことがわかります。ただし、名前による呼び出しバージョンでは、副作用が 2 回発生しました。
これは、値渡し関数が、関数を呼び出す前に渡された式の値を計算するためです。したがって、同じ値が毎回アクセスされます。代わりに、名前による呼び出し関数は、アクセスされるたびに、渡された式の値を再計算します。
Martin Odersky の例を次に示します。
def test (x:Int, y: Int)= x*x
評価戦略を調べて、次の条件でどちらが速い (ステップが少ない) かを判断します。
test (2,3)
値による呼び出し: test(2,3) -> 2*2 -> 4
名前による呼び出し: test(2,3) -> 2*2 -> 4
ここで、同じステップ数で結果に到達します。
test (3+4,8)
値による呼び出し: test (7,8) -> 7*7 -> 49
名前による呼び出し: (3+4) (3+4) -> 7 (3+4)-> 7*7 ->49
ここで call値による方が高速です。
test (7,2*4)
値による呼び出し: test(7,8) -> 7*7 -> 49
名前による呼び出し: 7 * 7 -> 49
ここでは、名前による呼び出しの方が高速です
test (3+4, 2*4)
値による呼び出し: test(7,2*4) -> test(7, 8) -> 7*7 -> 49
名前による呼び出し: (3+4) (3+4) -> 7 (3+4) -> 7*7 -> 49
同じ手順で結果に到達します。
あなたの例の場合、valueでのみ定義しているため、 function で呼び出される前にすべてのパラメーターが評価されます。パラメータを名前で定義する場合は、コード ブロックを渡す必要があります。
def f(x: => Int, y:Int) = x
このようにして、パラメーターは関数で呼び出されるまでx
評価されません。
こちらの小さな投稿でも、これをうまく説明しています。
通常、関数のパラメーターは値渡しパラメーターです。つまり、パラメーターの値は、関数に渡される前に決定されます。しかし、関数内で呼び出されるまで評価したくない式をパラメーターとして受け入れる関数を作成する必要がある場合はどうでしょうか? この状況のために、Scala は名前による呼び出しパラメーターを提供します。
名前による呼び出しメカニズムはコード ブロックを呼び出し先に渡し、呼び出し先がパラメーターにアクセスするたびにコード ブロックが実行され、値が計算されます。
object Test {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
t
}
}
1. C:/>scalac Test.scala 2.スカラテスト 3.遅延法で 4.ナノ秒単位で時間を取得する 5. パラメータ: 81303808765843 6.ナノ秒単位で時間を取得する
私が想定しているように、call-by-value
上記の関数は値だけを関数に渡します。It is a Evaluation strategy によるとMartin Odersky
、関数評価で重要な役割を果たす Scala が続きます。しかし、それを簡単にしてくださいcall-by-name
。としても知られているメソッドへの引数として関数を渡すようなものHigher-Order-Functions
です。メソッドが渡されたパラメーターの値にアクセスすると、渡された関数の実装が呼び出されます。以下のように:
@dhg の例に従って、最初に次のようにメソッドを作成します。
def something() = {
println("calling something")
1 // return value
}
この関数には 1 つのprintln
ステートメントが含まれ、整数値を返します。として引数を持つ関数を作成しますcall-by-name
。
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
この関数パラメーターは、1 つの整数値を返す無名関数を定義します。これには、引数を渡したが値を返すx
関数の定義が含まれており、関数には同じ署名が含まれています。関数を呼び出すとき、関数を引数として に渡します。ただし、整数値のみを関数に渡す場合。以下のように関数を呼び出します。0
int
something
callByName
call-by-value
scala> callByName(something())
calling something
x1=1
calling something
x2=1
このメソッドは 2 回呼び出されます。これは、 inメソッドsomething
の値にアクセスすると、メソッドの定義が呼び出されるためです。x
callByName
something
Call by Valueでは、関数呼び出し時に式の値が事前に計算され、その特定の値がパラメータとして対応する関数に渡されます。関数全体で同じ値が使用されます。
一方、Call by Nameでは、式自体がパラメーターとして関数に渡され、その特定のパラメーターが呼び出されるたびに関数内でのみ計算されます。
Scala での名前による呼び出しと値による呼び出しの違いは、次の例でよりよく理解できます。
コードスニペット
object CallbyExample extends App {
// function definition of call by value
def CallbyValue(x: Long): Unit = {
println("The current system time via CBV: " + x);
println("The current system time via CBV " + x);
}
// function definition of call by name
def CallbyName(x: => Long): Unit = {
println("The current system time via CBN: " + x);
println("The current system time via CBN: " + x);
}
// function call
CallbyValue(System.nanoTime());
println("\n")
CallbyName(System.nanoTime());
}
出力
The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521
The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589
上記のコード スニペットでは、関数呼び出しCallbyValue(System.nanoTime())に対して、システム ナノ時間が事前に計算され、事前に計算された値がパラメーターとして関数呼び出しに渡されています。
ただし、CallbyName(System.nanoTime())関数呼び出しでは、式 "System.nanoTime())" 自体がパラメーターとして関数呼び出しに渡され、そのパラメーターが関数内で使用されると、その式の値が計算されます。 .
CallbyName 関数の関数定義に注意してください。パラメータxとそのデータ型が=>記号で区切られています。そこにあるその特定の記号は、関数が名前型で呼び出されることを示しています。
つまり、値による呼び出し関数の引数は、関数に入る前に 1 回評価されますが、名前による呼び出し関数の引数は、必要な場合にのみ関数内で評価されます。
お役に立てれば!
例を見ていくと、違いをよりよく理解できるはずです。
現在の時刻を返す単純な関数を定義しましょう。
def getTime = System.currentTimeMillis
ここで、1 秒遅れて 2 回出力する関数をnameで定義します。
def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}
そして値による1 :
def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}
それでは、それぞれを呼び出しましょう。
getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325
getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846
結果は違いを説明するはずです。スニペットはこちらから入手できます。
インターネットには、この質問に対するすばらしい回答がすでにたくさんあります。誰かが役に立つかもしれない場合に備えて、このトピックについて集めたいくつかの説明と例をまとめて書きます
前書き
値による呼び出し (CBV)
通常、関数のパラメーターは値渡しパラメーターです。つまり、パラメーターは左から右に評価され、関数自体が評価される前に値が決定されます。
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
名前呼び(CBN)
しかし、関数内で呼び出されるまで評価しない式をパラメーターとして受け入れる関数を作成する必要がある場合はどうでしょうか? この状況のために、Scala は名前による呼び出しパラメーターを提供します。つまり、パラメーターはそのまま関数に渡され、その評価は置換後に行われます
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
名前による呼び出しメカニズムは、コード ブロックを呼び出しに渡し、呼び出しがパラメーターにアクセスするたびに、コード ブロックが実行され、値が計算されます。次の例では、delayed はメソッドが入力されたことを示すメッセージを出力します。次に、delayed はメッセージとその値を出力します。最後に、delayed は 't' を返します。
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
遅延メソッド
で時間をナノ秒単位で取得
Param: 2027245119786400
ケースごとの長所と短所
CBN: + より頻繁に終了する * 上記の終了をチェックする * + 関数本体の評価で対応するパラメーターが使用されていない場合、関数の引数が評価されないという利点がある - 遅く、より多くのクラスが作成される (つまり、プログラムが読み込みに時間がかかります)、より多くのメモリを消費します。
CBV: + CBN より指数関数的に効率的であることがよくあります。これは、名前による呼び出しに伴う引数式の繰り返しの再計算が回避されるためです。すべての関数の引数を 1 回だけ評価します + 式がいつ評価されるかをよりよく知る傾向があるため、命令型の効果と副作用をうまく処理します。- パラメータの評価中にループが発生する可能性があります * 上記の終了を確認してください *
終了が保証されていない場合はどうなりますか?
-式 e の CBV 評価が終了すると、e の CBN 評価も終了します -他の方向は真ではありません
非終了の例
def first(x:Int, y:Int)=x
式 first(1,loop) を検討してください
CBN: first(1,loop) → 1 CBV: first(1,loop) → この式の引数を減らします。1つはループなので、引数を無限に減らします。終わらない
ケースごとの動作の違い
なるメソッドテストを定義しましょう
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Case1 テスト(2,3)
test(2,3) → 2*2 → 4
すでに評価された引数から開始するため、値による呼び出しと名前による呼び出しのステップ数は同じになります。
Case2 テスト(3+4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
この場合、値による呼び出しはより少ないステップを実行します
Case3 テスト(7, 2*4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
2 番目の引数の不要な計算を回避します
Case4 テスト(3+4, 2*4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
異なるアプローチ
まず、副作用のある関数があると仮定しましょう。この関数は何かを出力し、Int を返します。
def something() = {
println("calling something")
1 // return value
}
ここで、1 つは値による呼び出しスタイル (x: Int) で引数を取り、もう 1 つは名前による呼び出しスタイル (x: => 整数)。
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
副作用関数でそれらを呼び出すとどうなるでしょうか?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
したがって、値渡しバージョンでは、渡された関数呼び出し (something()) の副作用が 1 回だけ発生したことがわかります。ただし、名前による呼び出しバージョンでは、副作用が 2 回発生しました。
これは、値渡し関数が、関数を呼び出す前に渡された式の値を計算するためです。したがって、同じ値が毎回アクセスされます。ただし、名前による呼び出し関数は、アクセスされるたびに、渡された式の値を再計算します。
CALL-BY-NAME を使用したほうがよい例
から: https://stackoverflow.com/a/19036068/1773841
簡単なパフォーマンスの例: ロギング。
次のようなインターフェースを想像してみましょう。
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
そして、このように使用されます:
logger.info("Time spent on X: " + computeTimeSpent)
info メソッドが何もしない場合 (たとえば、ログ レベルがそれより高く設定されているため)、computeTimeSpent が呼び出されることはなく、時間を節約できます。これはロガーでよく発生し、ログに記録されるタスクに比べてコストがかかる可能性のある文字列操作がよく見られます。
正しさの例: 論理演算子。
おそらく次のようなコードを見たことがあるでしょう:
if (ref != null && ref.isSomething)
&& メソッドを次のように宣言するとします。
trait Boolean {
def &&(other: Boolean): Boolean
}
次に、ref が null の場合は常にエラーが発生します。これは、&& に渡される前に isSomething が null 参照で呼び出されるためです。このため、実際の宣言は次のとおりです。
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
CallByName
使用時にcallByValue
呼び出され、ステートメントが検出されるたびに呼び出されます。
例えば:-
無限ループがあります。つまり、この関数を実行すると、scala
プロンプトが表示されません。
scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int
callByName
関数は上記のメソッドを引数として取り、loop
その本体内で使用されることはありません。
scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int
メソッドの実行では、関数内でループ関数を使用してcallByName
いないため、問題は見つかりません (プロンプトが返されます) 。scala
callByName
scala> callByName(1,loop(10))
res1: Int = 1
scala>
callByValue
関数は上記のメソッドをパラメーターとして取ります。その結果、関数または式内の結果が評価されてから、再帰的に実行される関数loop
によって外側の関数が実行され、プロンプトが返されません。loop
scala
scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int
scala> callByValue(1,loop(1))
ここでのすべての回答が正しい理由を示しているとは思いません。
値による呼び出しでは、引数は 1 回だけ計算されます。
def f(x : Int, y :Int) = x
// following the substitution model
f(12 + 3, 4 * 11)
f(15, 4194304)
15
上記で、すべての引数が必要かどうかにかかわらず評価されることがわかります。通常call-by-value
は高速ですが、この場合は常にそうであるとは限りません。
評価戦略が次の場合call-by-name
、分解は次のようになります。
f(12 + 3, 4 * 11)
12 + 3
15
上記でわかるように、評価する必要がないため4 * 11
、計算を少し節約できます。これは、場合によっては有益です。