Hakell では、where 句は関数に対するローカル定義を保持します。Scala には明示的な where 句はありませんが、 local var
、val
andを使用することで同じ機能を実現できますdef
。
ローカルの「var」と「val」
スカラの場合:
def foo(x: Int, y: Int): Int = {
val a = x + y
var b = x * y
a - b
}
ハスケルでは:
foo :: Integer -> Integer -> Integer
foo x y = a - b
where
a = x + y
b = x * y
ローカルの「def」
スカラで
def foo(x: Int, y: Int): Int = {
def bar(x: Int) = x * x
y + bar(x)
}
ハスケルで
foo :: Integer -> Integer -> Integer
foo x y = y + bar x
where
bar x = x * x
現在、このコンピュータには Haskell コンパイラがインストールされていないため、Haskell の例で構文エラーが発生した場合は修正してください :)。
より複雑な例は、同様の方法で実現できます (たとえば、両方の言語がサポートしているパターン マッチングを使用します)。ローカル関数は、他の関数とまったく同じ構文を持ちますが、そのスコープはそれらが含まれるブロックです。
編集:また、そのような例と主題に関するいくつかの詳細については、ダニエルの答えを参照してください。
EDIT 2lazy
var
: s とsに関する議論を追加しましたval
。
怠惰な「var」と「val」
Edward Kmettの答えは、Haskell の where 句には怠惰と純粋性があることを正しく指摘しています。lazy
変数を使用して、Scala で非常によく似たことができます。これらは、必要な場合にのみインスタンス化されます。次の例を検討してください。
def foo(x: Int, y: Int) = {
print("--- Line 1: ");
lazy val lazy1: Int = { print("-- lazy1 evaluated "); x^2}
println();
print("--- Line 2: ");
lazy val lazy2: Int = { print("-- lazy2 evaluated "); y^2}
println();
print("--- Line 3: ");
lazy val lazy3: Int = { print("-- lazy3 evaluated ")
while(true) {} // infinite loop!
x^2 + y^2 }
println();
print("--- Line 4 (if clause): ");
if (x < y) lazy1 + lazy2
else lazy2 + lazy1
}
ここでlazy1
、lazy2
とlazy3
はすべて遅延変数です。lazy3
はインスタンス化されず (したがって、このコードは無限ループに入ることはありません)、 および のインスタンス化の順序は関数の引数lazy1
にlazy2
依存します。たとえば、呼び出すと前foo(1,2)
にlazy1
インスタンス化されlazy2
、呼び出すとfoo(2,1)
逆になります。コードを scala インタープリターで試してみて、プリントアウトを確認してください! (この答えはすでにかなり長いので、ここには入れません)。
遅延変数の代わりに引数のない関数を使用すると、同様の結果が得られます。lazy val
上記の例では、everyを aに置き換えて、def
同様の結果を得ることができます。違いは、遅延変数はキャッシュされます (つまり、一度だけ評価されます) が、adef
は呼び出されるたびに評価されます。
編集 3:スコーピングに関する議論を追加しました。質問を参照してください。
ローカル定義の範囲
ローカル定義には、予想どおり、それらが宣言されているブロックのスコープがあります (ほとんどの場合、 for ループで中間ストリームの変数バインディングを使用する場合など、まれな状況でブロックをエスケープできます) 。したがって local var
、val
およびdef
式の範囲を制限するために使用できます。次の例を見てください。
object Obj {
def bar = "outer scope"
def innerFun() {
def bar = "inner scope"
println(bar) // prints inner scope
}
def outerFun() {
println(bar) // prints outer scope
}
def smthDifferent() {
println(bar) // prints inner scope ! :)
def bar = "inner scope"
println(bar) // prints inner scope
}
def doesNotCompile() {
{
def fun = "fun" // local to this block
42 // blocks must not end with a definition...
}
println(fun)
}
}
との両方が期待どおりinnerFun()
にouterFun()
動作します。bar
inの定義は、囲んでいるスコープの定義をinnerFun()
隠します。bar
また、関数fun
はそれを囲むブロックに対してローカルであるため、それ以外の方法では使用できません。メソッドdoesNotCompile()
... はコンパイルされません。メソッド printprintln()
からの両方の呼び出しに注意するのは興味深いことです。したがって、はい、メソッド内で使用された後に定義を配置できます! ただし、それは悪い習慣だと思うので、お勧めしません(少なくとも私の意見では)。クラス ファイルでは、メソッド定義を好きなように配置できますが、使用する前にすべての s を関数内に保持します。とs とs ... うーん... 使った後に置くのは面倒です。smthDifferent()
inner scope
def
val
var
また、各ブロックは定義ではなく式で終了する必要があるため、ブロックの最後にすべての定義を含めることはできません。おそらく、すべての定義をブロックの先頭に配置し、そのブロックの最後に結果を生成するすべてのロジックを記述します。次の方法ではなく、その方が自然に思えます。
{
// some logic
// some defs
// some other logic, returning the result
}
前に言ったように、ブロックを だけで終わらせることはできません// some defs
。ここが、Scala が Haskell と少し異なるところです :)。
EDIT 4 : Kimのコメントに促されて、それらを使用した後にそれらを定義することについて詳しく説明しました。
それらを使用した後に「もの」を定義する
これは、副作用のある言語で実装するのが難しいことです。純粋な副作用のない世界では、順序は重要ではありません (メソッドは副作用に依存しません)。しかし、Scala では副作用が許容されるため、関数を定義する場所は重要です。val
また、またはを定義する場合var
、その をインスタンス化するために右側を適切に評価する必要がありますval
。次の例を検討してください。
// does not compile :)
def foo(x: Int) = {
// println *has* to execute now, but
// cannot call f(10) as the closure
// that you call has not been created yet!
// it's similar to calling a variable that is null
println(f(10))
var aVar = 1
// the closure has to be created here,
// as it cannot capture aVar otherwise
def f(i: Int) = i + aVar
aVar = aVar + 1
f(10)
}
あなたが与える例は、val
sがlazy
またはsである場合でも機能しますdef
。
def foo(): Int = {
println(1)
lazy val a = { println("a"); b }
println(2)
lazy val b = { println("b"); 1 }
println(3)
a + a
}
この例は、キャッシングが機能していることもよく示しています ( to を変更して、何が起こるか見てみましょlazy val
うdef
:)
私はまだ、副作用のある世界では、定義を使用する前に定義することに固執する方がよいと考えています. このようにすると、ソース コードが読みやすくなります。
-- Flaviu Cipcigan