(3) var, val and lazy

(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 (like final in Java, or constants 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.

2 thoughts on “(3) var, val and lazy

  1. Victoria Liao Post author

    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

Leave a Reply

Your email address will not be published. Required fields are marked *