きくらげ観察日記

好きなことを、適当に。

定数(に見えるもの)の定義いろいろ

Scalaでは、例えばInt型の値xを定義するのにも次のような複数の方法があります

scala> val x: Int = 3
scala> lazy val x: Int = 3
scala> def x: Int = 3

今回はこれらの違いについてメモしておきます。

valでの定義

これが一番一般的な定義の方法です。

val x: Int = 3

という文が表れた瞬間に右辺の値を評価し、左辺の変数に代入します。

lazy valでの定義

lazy修飾子を付けると、値が必要になるまで評価が遅延されます。

lazy val x: Int = 3

しかし、Haskellの遅延評価とは違い、本当に必要になる前に評価されてしまう場合があります。
関数の引数としてxが渡された場合、実際にはその関数の中でxが使われなかったとしても、xの中身は評価されてしまいます。

scala> def k[T, U](x: T, y: U) = x
scala> lazy val x: Int = { println("evaluated"); 3 }
scala> k(0, x)
evaluated
res1: Int = 0

なので、単にlazyとつけただけでHaskellのように無限リストが作れるというわけでもありません。注意が必要です。

defでの定義

defで定義すると、引数なしの関数として扱われます。値は必要になる度に評価されることになります。

scala> def x: Int = { println("called"); 3 }
scala> x
called
res5: Int = 3

scala> x
called
res6: Int = 3

scala> k(0, x) // lazy valのときと同様に、この場合でもxの値は評価される。
called
res7: Int = 0

ちなみに、関数だからと言って「()」を付けて呼ぶことはできません。

引数で受け取る場合

関数の引数として値を受け取る場合は、「=> T」という形で型を指定することで、必要になり次第引数を評価することができるようになります。

scala> def k_[T, U](x: T, y: => U): T = x // 先ほどのkの、第2引数を評価しないバージョン
k_: [T, U](x: T, y: => U)T
scala> lazy val x = { println("evaluated"); 3 }
x: Int = <lazy>
scala> k_(0, x)
res10: Int = 0

注意しなければならないのは、「=> T」は意味的には「() => T」とほぼ同様のものなので、引数の扱いはdefで引数無しの関数を定義したときと同じようになります。

scala> def twice(a: => Int) = a + a
scala> def x: Int = { println("called"); 3 }
scala> twice(x)
called
called
res11: Int = 6

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版