(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:
valcreates an immutable variable (likefinalin Java, orconstantsin other languages)varcreates 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 == 0even eq even
// Workspace result:
// def even: Int => Boolean
// val res1: Boolean = false