0

I tried searching stackoverflow and there are a number of relevant topics including

val-mutable versus var-immutable in Scala

What is the difference between a var and val definition in Scala?

But I still want to clarify if my understanding is correct.

Looks like the rule is following

Prefer immutable val over immutable var over mutable val over mutable var. Especially immutable var over mutable val!

But performance of immutable var is much worse vs mutable val according to simple test in REPL

scala -J-Xmx2g

scala> import scala.collection.mutable.{Map => MMap}
import scala.collection.mutable.{Map=>MMap}

scala>

scala> def time[R](block: => R): R = {
     |   val t0 = System.nanoTime()
     |   val result = block    // call-by-name
     |   val t1 = System.nanoTime()
     |   println("Elapsed time: " + (t1 - t0) + "ns")
     |   result
     | }
time: [R](block: => R)R

scala>time {val mut_val = MMap[Int, Int](); for (i <- 1 to 1000000) mut_val += (i -> i)}
Elapsed time: 551073900ns

scala>time {var mut_var = MMap[Int, Int](); for (i <- 1 to 1000000) mut_var += (i -> i)}
Elapsed time: 574174400ns

scala>time {var imut_var = Map[Int, Int](); for (i <- 1 to 1000000) imut_var += (i -> i)}
Elapsed time: 860938800ns

scala>time {val mut_val = MMap[Int, Int](); for (i <- 1 to 2000000) mut_val += (i -> i)}
Elapsed time: 1103283000ns

scala>time {var mut_var = MMap[Int, Int](); for (i <- 1 to 2000000) mut_var += (i -> i)}
Elapsed time: 1166532600ns

scala>time {var imut_var = Map[Int, Int](); for (i <- 1 to 2000000) imut_var += (i -> i)}
Elapsed time: 2926954500ns

I added also mutable var even tough it does not make much sense. It's performance is quite similar to mutable val, just to complete the picture. But performance of immutable var is much worse.

So the price for immutable var vs mutable val is degraded performance (and more extensive memory usage!). Can someone please explain again what is the point of paying that price? Specific examples are appreciated.

Thanks

Dr Y Wit
  • 2,000
  • 9
  • 16
  • 1
    You would never add single elements one after another to an immutable map like that. You would rather do something like `(1 to 2000000).view.map(i => i -> i).toMap`, which should be faster. – marstran Jul 31 '18 at 12:39
  • 2
    The advantages of immutability are outlined all over the Internet. What about the research that you've done are you asking about specifically? – Carcigenicate Jul 31 '18 at 12:40
  • 2
    OK, you tested that building a 2M-element map iteratively will be faster with a mutable map. That's expected. But you cannot generalise this result that using mutable structures everywhere is faster and simpler. – Bergi Jul 31 '18 at 13:47
  • @Carcigenicate, I though the question is quite clear. For, example, neither mut val no imut var are "referentially transparent". None of the are thread safe. Etc. Expected answer is a specific reason(s) why imut var may be better - like explained in the kag0's answer. – Dr Y Wit Aug 01 '18 at 01:04
  • @DrYWit What you're asking is clear, but asking what the benefits of immutability are is an overly vague question that's, as I mentioned, already answered with a little research. If you needed clarification on a specific point based on prior research, that would have been a good question to ask. – Carcigenicate Aug 01 '18 at 01:08
  • @Carcigenicate, if it's clear so why you treat it as "benefits of immutability"? The question is specifically about imut var vs mut val. If it does not makes sense for you to explain why imut var is better than mut val then, probably, you could explain why mut val may be worse than imut var like kag0 did. Also you could have just provided a link if this specific question is answered. I would be happy to delete it as a duplicate. – Dr Y Wit Aug 01 '18 at 01:18

1 Answers1

1

There are a lot of ways to answer this question (and many comments to refute the claim that a mutable val always performs worse), but here's one in a phrase: scope and side effects.

The scope of an immutable var is limited to where it was declared, and passing it around as a parameter or assigning it to another variable usually behaves as expected.
A mutable val on the other hand will feel state changes no matter where it's used and passed.

Consider an application where you want to run several workers with different configurations that inherit from a default. (assume TIMEOUT_1 = t1 and PARALLEL_FACTOR_1 = pf1 and TIMEOUT_2 = t2, etc)

With immutable vars

var defaultConfig = immutable.Map("timeout" → "5", "parallelFactor" → "4")
var worker1Config = defaultConfig
var worker2Config = defaultConfig

worker1Config += "timeout" → sys.env("TIMEOUT_1")
worker1Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_1")

worker2Config += "timeout" → sys.env("TIMEOUT_2")
worker2Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_2")

println(defaultConfig) // Map(timeout -> 5, parallelFactor -> 4)
println(worker1Config) // Map(timeout -> t1, parallelFactor -> pf1)
println(worker2Config) // Map(timeout -> t2, parallelFactor -> pf2)

With mutable vals

val defaultConfig = mutable.Map("timeout" → "5", "parallelFactor" → "4")
val worker1Config = defaultConfig
val worker2Config = defaultConfig

worker1Config += "timeout" → sys.env("TIMEOUT_1")
worker1Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_1")

worker2Config += "timeout" → sys.env("TIMEOUT_2")
worker2Config += "parallelFactor" → sys.env("PARALLEL_FACTOR_2")

println(defaultConfig) // Map(parallelFactor -> pf2, timeout -> t2)
println(worker1Config) // Map(parallelFactor -> pf2, timeout -> t2)
println(worker2Config) // Map(parallelFactor -> pf2, timeout -> t2)

You can see that even though almost all the code is the same, using mutable vals has introduced a non-obvious bug (especially if these sections of code were in different functions rather than all together).

kag0
  • 5,624
  • 7
  • 34
  • 67
  • Great example, thanks. It's quite obvious why defaultConfig but what was a bit unexpected is that "order" in the mutable map was not preserved. I tested with only one working config and 3 (key -> value) pairs worker1Config += "timeout" -> "t1"; worker1Config += "parallelFactor" -> "pf1"; worker1Config += "dummy" -> "dummy" and result is below scala.collection.mutable.Map[String,String] = Map(parallelFactor -> pf1, dummy -> dummy, timeout -> t1) – Dr Y Wit Aug 01 '18 at 00:49