(3) var, val and lazy
About
:octocat: GitHub: All of the example code: repo (link)
:page_facing_up: blog link: https://purrgramming.life/cs/programming/fp/ :star:
var vs val
In Java, you declare new variables like this:
String s = "hello";
int i = 42;
Person p = new Person("Victoria Pinzhen Liao");
Each variable declaration is preceded by its type.
Scala has two types of variables:
val
creates an immutable variable (likefinal
in Java, orconstants
in other languages)var
creates a mutable variable
This is what variable declaration looks like in Scala:
// variable’s type is _inferred_ by the compiler
val s = "hello" // immutable
var i = 42 // mutable
class Person(name: String)
val p = new Person("Victoria Pinzhen Liao")
Those examples show that the Scala compiler is usually smart enough to infer the variable’s data type from the code on the right side of the =
sign. We say that the variable’s type is inferred by the compiler.
You can also explicitly declare the variable type if you prefer:
// _explicitly_ declare the variable type
val s: String = "hello"
var i: Int = 42
Performance
val
and var
are evaluated when defined.
val
vs var
val
makes a variable immutable — like final
in Java, i.e. values
var
makes a variable mutable. i.e. variables
val a = 'a'
a = 'b'
Result
<console>:12: error: reassignment to val
That fails with a reassignment to val
error, as expected. Conversely, you can reassign a var
:
var c = 'c'
c = 'd' // This is fine
Result
c: Char = d
Which One?
The general rule is that you should always use a val
field unless there is a good
reason not to.
- makes your code more like algebra
- helps get you started down the path to functional programming, where all fields are immutable.
val
in REPL
The REPL is not 100% the same as working with source code in an IDE, so you can do a few things in the REPL that you cannot
do when working on real-world code in a project.
You can redefine a val
field in the REPL, like this:
// It's ok only in REPL
scala> val age = 18
age: Int = 18
scala> val age = 19
age: Int = 19
val
fields cannot be redefined like that in the real world, but they can be redefined in the REPL playground.
val vs def
We use the keyword’ def to introduce a definition evaluated only when used.`
def
introduces a definition where the right-hand side is evaluated on each use.
While the def
is a function declaration, it is evaluated on call, i.e. val
evaluates when defined, def
– when called:
Example:
// Complain immediately
scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing
// Complain on call
scala> def even: Int => Boolean = ???
even: Int => Boolean
scala> even
scala.NotImplementedError: an implementation is missing
Function Identity
Method def even
evaluates on call and creates new function every time (new instance of Function1
).
def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false
val evenVal: Int => Boolean = _ % 2 == 0
evenVal eq evenVal
//Boolean = true
Function Results
With def
you can get new function on every call:
val randomInt: () => Int = {
val r = util.Random.nextInt
() => r
}
randomInt() // val res3: Int = 1764655189
randomInt() // val res4: Int = 1764655189
// --------------------------------
def randomIntDef: () => Int = {
val r = util.Random.nextInt
() => r
}
// Different
randomIntDef()
randomIntDef()
lazy val
lazy val
is evaluated when called the first time:
scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>
scala> even
scala.NotImplementedError: an implementation is missing
But returns the same result (in this case same instance of FunctionN
) every time:
Identity
lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
Results
lazy val randomInt: () => Int = {
val r = util.Random.nextInt
() => r
}
randomInt()
// Int = -1068569869
randomInt()
// Int = -1068569869 - same result
Performance
val
is evaluated when defined.
def
is evaluated on every call so that performance could be worse than val
for multiple calls. You will get the same performance with a single call. Furthermore, with no calls, you will get no overhead from def
, so you can define it even if you do not use it in some branches.
You will get a lazy evaluation with a lazy val
: you can define it even if you do not use it in some branches. It evaluates once or never, but you will get a little overhead from double-checking locking on every access to your lazy val
.
However, if you need a function (not a method) for function composition or higher-order functions (like filter(even)
) compiler will generate a function from your method every time you are using it as a function so that performance could be slightly worse than with val
.
This is true
Hi Bob : )
If you can reproduce this in the workspace with
def even: Int => Boolean = _ % 2 == 0
even eq even
// Workspace result:
// def even: Int => Boolean
// val res1: Boolean = false