How does foo(a)(b) += x work in Scala? - scala

I have been counting occurences with a mutable map:
var bar = collection.mutable.Map[Int, Int]().withDefaultValue(0)
Now bar(a) += b works just fine, no matter if the key a is present in bar already or not (in which case it will then be added).
I tried the same thing with a mutable map of mutable maps:
var foo = collection.mutable.Map[Int, collection.mutable.Map[Int, Int]]().
withDefaultValue(collection.mutable.Map().withDefaultValue(0))
How does foo(a)(b) += x look without syntactic sugar?
Using What are all the instances of syntactic sugar in Scala? I would assume it expands to:
foo.apply(a).put(b, foo.apply(a).apply(b) + x)
But why does this not update foo itself accordingly like in the introductory example (i.e. foo will not have a dedicated value for key a if it wasn't present before)?
Edit: As Perseids pointed out, foo(a)(b) += x will change the mutable default value. Is this a desired feature?
Using getOrElseUpdate as suggested by DaoWen seems to be the best way to overcome both problems. But while that works well for functions of type Int => Int => Int it becomes really cumbersome for functions of type Int => Int => Int => Int => Int. So I'm still happy for any suggestions!

This is actually an issue with withDefaultValue, not the += operator. Your first withDefaultValue for foo returns a new mutable map if the given key does not exist. When you do the lookup foo(a)(b), if foo(a) doesn't exist, then it returns a new map, which we'll call tmp. foo(a)(b) += x then essentially expands to this:
val tmp = foo(a)
tmp(b) += x
The problem is that only tmp is being updated by +=, not foo. So your update is happening on tmp, but tmp gets thrown away after the call because it's never stored anywhere.
If you want your parent map to get updated, you might want to look into using getOrElseUpdate instead of relying on withDefaultValue.
Note: As Perseids points out in the comments below, withDefaultValue takes a by-value parameter. This means that every time you get an unset key from your map, it's going to return the same mutable map instance! This is yet another reason you should think about using getOrElseUpdate (which uses a by-name parameter), or at least withDefault, which takes a function. (That's all assuming that you actually want different map instances for each slot in your map...)

How does foo(a)(b) += x look without syntactic sugar?
It depends on whether or not the object returned by foo(a)(b) has a method named += or not. If it does, then it's equivalent to:
foo.apply(a).apply(b).+=(x)
If it doesn't, then it's equivalent to:
foo.apply(a).update(b, foo.apply(a).apply(b).+(x))
Except without the duplicate evaluation of foo.apply(a) (I think.)

Related

Scala: Update Array inside a Map

I am creating a Map which has an Array inside it. I need to keep adding values to that Array. How do I do that?
var values: Map[String, Array[Float]] = Map()
I tried several ways such as:
myobject.values.getOrElse("key1", Array()).++(Array(float1))
Few other ways to but nothing updates the array inside the Map.
There is a problem with this code:
values.getOrElse("key1", Array()).++(Array(float1))
This does not update the Map in values, it just creates a new Array and then throws it away.
You need to replace the original Map with a new, updated Map, like this:
values = values.updated("key1", values.getOrElse("key1", Array.empty[Float]) :+ float1)
To understand this you need to be clear on the distinction between mutable variables and mutable data.
var is used to create a mutable variable which means that the variable can be assigned a new value, e.g.
var name = "John"
name = "Peter" // Would not work if name was a val
By contrast mutable data is held in objects whose contents can be changed
val a = Array(1,2,3)
a(0) = 12 // Works even though a is a val not a var
In your example values is a mutable variable but the Map is immutable so it can't be changed. You have to create a new, immutable, Map and assign it to the mutable var.
From what I can see (according to ++), you would like to append Array, with one more element. But Array fixed length structure, so instead I'd recommend to use Vector. Because, I suppose, you are using immutable Map you need update it as well.
So the final solution might look like:
var values: Map[String, Vector[Float]] = Map()
val key = "key1"
val value = 1.0
values = values + (key -> (values.getOrElse(key, Vector.empty[Float]) :+ value))
Hope this helps!
You can use Scala 2.13's transform function to transform your map anyway you want.
val values = Map("key" -> Array(1f, 2f, 3f), "key2" -> Array(4f,5f,6f))
values.transform {
case ("key", v) => v ++ Array(6f)
case (_,v) => v
}
Result:
Map(key -> Array(1.0, 2.0, 3.0, 6.0), key2 -> Array(4.0, 5.0, 6.0))
Note that appending to arrays takes linear time so you might want to consider a more efficient data structure such as Vector or Queue or even a List (if you can afford to prepend rather than append).
Update:
However, if it is only one key you want to update, it is probably better to use updatedWith:
values.updatedWith("key")(_.map(_ ++ Array(6f)))
which will give the same result. The nice thing about the above code is that if the key does not exist, it will not change the map at all without throwing any error.
Immutable vs Mutable Collections
You need to choose what type of collection you will use immutable or mutable one. Both are great and works totally differently. I guess you are familiar with mutable one (from other languages), but immutable are default in scala and probably you are using it in your code (because it doesn't need any imports). Immutable Map cannot be changed... you can only create new one with updated values (Tim's and Ivan's answers covers that).
There are few ways to solve your problem and all are good depending on use case.
See implementation below (m1 to m6):
//just for convenience
type T = String
type E = Long
import scala.collection._
//immutable map with immutable seq (default).
var m1 = immutable.Map.empty[T,List[E]]
//mutable map with immutable seq. This is great for most use-cases.
val m2 = mutable.Map.empty[T,List[E]]
//mutable concurrent map with immutable seq.
//should be fast and threadsafe (if you know how to deal with it)
val m3 = collection.concurrent.TrieMap.empty[T,List[E]]
//mutable map with mutable seq.
//should be fast but could be unsafe. This is default in most imperative languages (PHP/JS/JAVA and more).
//Probably this is what You have tried to do
val m4 = mutable.Map.empty[T,mutable.ArrayBuffer[E]]
//immutable map with mutable seq.
//still could be unsafe
val m5 = immutable.Map.empty[T,mutable.ArrayBuffer[E]]
//immutable map with mutable seq v2 (used in next snipped)
var m6 = immutable.Map.empty[T,mutable.ArrayBuffer[E]]
//Oh... and NEVER DO THAT, this is wrong
//I mean... don't keep mutable Map in `var`
//var mX = mutable.Map.empty[T,...]
Other answers show immutable.Map with immutable.Seq and this is preferred way (or default at least). It costs something but for most apps it is perfectly ok. Here You have nice source of info about immutable data structures: https://stanch.github.io/reftree/talks/Immutability.html.
Each variant has it's own Pros and Cons. Each deals with updates differently, and it makes this question much harder than it looks at the first glance.
Solutions
val k = "The Ultimate Answer"
val v = 42f
//immutable map with immutable seq (default).
m1 = m1.updated(k, v :: m1.getOrElse(k, Nil))
//mutable map with immutable seq.
m2.update(k, v :: m2.getOrElse(k, Nil))
//mutable concurrent map with immutable seq.
//m3 is bit harder to do in scala 2.12... sorry :)
//mutable map with mutable seq.
m4.getOrElseUpdate(k, mutable.ArrayBuffer.empty[Float]) += v
//immutable map with mutable seq.
m5 = m5.updated(k, {
val col = m5.getOrElse(k, c.mutable.ArrayBuffer.empty[E])
col += v
col
})
//or another implementation of immutable map with mutable seq.
m6.get(k) match {
case None => m6 = m6.updated(k, c.mutable.ArrayBuffer(v))
case Some(col) => col += v
}
check scalafiddle with this implementations. https://scalafiddle.io/sf/WFBB24j/3.
This is great tool (ps: you can always save CTRL+S your changes and share link to write question about your snippet).
Oh... and if You care about concurrency (m3 case) then write another question. Such topic deserve to be in separate thread :)
(im)mutable api VS (im)mutable Collections
You can have mutable collection and still use immutable api that will copy orginal seq. For example Array is mutable:
val example = Array(1,2,3)
example(0) = 33 //edit in place
println(example.mkString(", ")) //33, 2, 3
But some functions on it (e.g. ++) will create new sequence... not change existing one:
val example2 = example ++ Array(42, 41) //++ is immutable operator
println(example.mkString(", ")) //33, 2, 3 //example stays unchanged
println(example2.mkString(", ")) //33, 2, 3, 42, 41 //but new sequence is created
There is method updateWith that is mutable and will exist only in mutable sequences. There is also updatedWith and it exists in both immutable AND mutable collections and if you are not careful enough you will use wrong one (yea ... 1 letter more).
This means you need to be careful which functions you are using, immutable or mutable one. Most of the time you can distinct them by result type. If something returns collection then it will be probably some kind of copy of original seq. It result is unit then it is mutable for sure.

What does an underscore after a scala method call mean?

The scala documentation has a code example that includes the following line:
val numberFunc = numbers.foldLeft(List[Int]())_
What does the underscore after the method call mean?
It's a partially applied function. You only provide the first parameter to foldLeft (the initial value), but you don't provide the second one; you postpone it for later. In the docs you linked they do it in the next line, where they define squares:
val numberFunc = numbers.foldLeft(List[Int]())_
val squares = numberFunc((xs, x) => xs:+ x*x)
See that (xs, x) => xs:+ x*x, that's the missing second parameter which you omitted while defining numberFunc. If you had provided it right away, then numberFunc would not be a function - it would be the computed value.
So basically the whole thing can also be written as a one-liner in the curried form:
val squares = numbers.foldLeft(List[Int]())((xs, x) => xs:+ x*x)
However, if you want to be able to reuse foldLeft over and over again, having the same collection and initial value, but providing a different function every time, then it's very convinient to define a separate numbersFunc (as they did in the docs) and reuse it with different functions, e.g.:
val squares = numberFunc((xs, x) => xs:+ x*x)
val cubes = numberFunc((xs, x) => xs:+ x*x*x)
...
Note that the compiler error message is pretty straightforward in case you forget the underscore:
Error: missing argument list for method foldLeft in trait
LinearSeqOptimized Unapplied methods are only converted to functions
when a function type is expected. You can make this conversion
explicit by writing foldLeft _ or foldLeft(_)(_) instead of
foldLeft. val numberFunc = numbers.foldLeft(ListInt)
EDIT: Haha I just realized that they did the exact same thing with cubes in the documentation.
I don't know if it helps but I prefer this syntax
val numberFunc = numbers.foldLeft(List[Int]())(_)
then numberFunc is basically a delegate corresponding to an instance method (instance being numbers) waiting for a parameter. Which later comes to be a lambda expression in the scala documentation example

Scala, collections, variable access scope

Sorry, I got even no idea how to name a title, as I believe this is a dead simple thing.
I have function which is taking Int as an argument and returning List of Ints (after adding values to int trough loop, and couple if statements. Signature is a must)
My problem:
def a(i:Int) = { var l2 = List(1,2); l2.+:(1); l2; }
println(a(3)) // outputs List(1, 2)
Why function a is returning List(1,2) instead of List(3,1,2) ??
And what would be a correct solution in this situation?
I really appreciate your time to help me.
The method +: that you are calling on a List returns a new list with the element prepended. The original list is not modified.
In the statement l2.+:(1) you are ignoring the return value (the new list with the element prepended). Then you return l2, which still refers to the original List which contains the two elements 1 and 2.
You are missing an assignment (and you probably meant i instead of 1): l2 +:= i.
But in scala it is preferable to avoid using var:
def a(i: Int) = { i :: List(1, 2) }
(I guess your method is much more complex, but it's almost always possible to rewrite it that way)

Scala type mismatch when adding an element to an array

I have the following array:
var as = Array.empty[Tuple2[Int, Int]]
I am adding an element to it like this:
var nElem = Tuple2(current, current)
as += nElem
current is a var of type Int
However, I am getting this error:
Solution.scala:51: error: type mismatch;
found : (Int, Int)
required: String
as += nElem
I don't understand why this is appearing. I haven't declared a String anywhere.
+= is the string concatenation operator.
You are looking for :+ to append to an array. Note, that Array length is immutable, so :+=, will return a new array, with the nElem appended, and assign it to the as variable, the original array will stay unchanged (take this as a hint, that you are likely doing something in a suboptimal way).
Note, that if you find yourself using var, that is almost always a sign of a bad design in your code. Mutable objects and variables are considered really bad taste in functional programming. Sometimes, you can't get away without using them, but those are rare corner cases. Most of the time, you should not need mutability.
Also, do not use Tuple2. Just do Array.empty[(Int, Int)], nElem = (current, current) etc.
Use a :+= to modify the variable in place. However, remember this: Using both var and a mutable data structure at the same time (like Array) is a sign of really bad programming. Either is sometimes fine, though.
However, note that this operation is O(n), therefore pushing n elements like that is going to be slow, O(n²). Arrays are not meant to have elements pushed to back like that. You can alternatively use a var Vector instead and call .toArray() on it at the end or use a mutable val ArrayBuffer. However, prefer functional style of programming, unless it produces less readable code.
Also, avoid typing Tuple2 explicitly. Use Array.empty[(Int, Int)] and var nElem = (current, current).
The semantics of + are weird because of the automatic conversion to String in certain cases. To append to an array, use the :+ method:
as :+= nElem

Scala puts precedence on implicit conversion over "natural" operations... Why? Is this a bug? Or am I doing something wrong?

This simple test, of course, works as expected:
scala> var b = 2
b: Int = 2
scala> b += 1
scala> b
res3: Int = 3
Now I bring this into scope:
class A(var x: Int) { def +=(y:Int) { this.x += y } }
implicit def int2A(i:Int) : A = new A(i)
I'm defining a new class and a += operation on it, and a convenient implicit conversion for those times when I want to add an Int to A's Int value.
I never expected this would affect the way my regular Int operations behave, when the "A" class is not at all part of the expression.
But it does:
scala> var b:Int = 0
b: Int = 0
scala> b += 1
scala> b
res29: Int = 0
scala> b += 2
scala> b
res31: Int = 0
What seems to be happening here is that the b:Int is implicitly converted to an "A", which is not bound to any variable, and then += is invoked on it, discarding the results.
Scala seems to give high precedence the implicit conversion over the natural += behavior (compiler magic, not an actual method) that is already defined to Ints. Common-sense as well as a C++ background tells me implicits should only be invoked as a last resort, when the compilation would otherwise fail. That leads to several questions...
Why? Is this a bug? Is it by design?
Is there a work-around (other than not using "+=" for my DSL's "+=" operation)?
Thanks
As others have noted, Int cannot have a += "method", because Int is immutable. What happens instead is that x += 1 is treated as a short form for x = x + 1, but only if there is no
method called += that is defined on the type. So method resolution takes precedence.
Given that Scala lets you define += methods and also lets you do += on variables, could we have changed the priority of the two? I.e. try expanded += first and only if that fails search for a method named +=?
Theoretically yes, but I argue it would have been worse than the current scheme. Practically, no. There are many types in Scala's collection library that define both a + method for
non-destructive addition and a += method for destructive addition. If we had switched the priority around then a call like
myHashTable += elem
would expand to
myHashTable = myHashTable + elem
So it would construct a new hashtable and assign this back to the variable, instead of simply updating an element. Not a wise thing to do...
From Programming in Scala, Chapter 17:
Whenever you write a += b, and a does
not support a method named +=, Scala
will try interpreting it as a = a + b.
The class Int does not contain method +=. However class A provides += method. That might be triggering the implicit conversion from Int to A.
I don't think it is a bug.
Actually, Int only has a "+" method but doesn't have a "+=" method.
b += 1 would transform to b = b + 1 in compile time if there is not a other implicit which has a "+=" method exists.
Scala seems to give high precedence the implicit conversion over the natural += that is already defined to Ints.
Which version of Scala are you talking about? I don't know of any version that has a += method on Int. Certainly, none of the still supported versions do, that must be some really ancient version you have there.
And since there is no += on Int, but you are calling a += method on Int, Scala tries to satisfy that type constraint via an implicit conversion.
Even withstanding Eastsun's explanation, it seems like this is a bug, and it should try the b=b+1 transformation before trying an implicit conversion for +=.
Please ask this question to the scala-user email list by emailing scala-user#listes.epfl.ch or by visiting n4.nabble.com/Scala-User-f1934582.html. If it's a bug, that's where it will be noticed and fixed.