Cannot modify Seq.head - scala

I'm learning Scala, and got confused about Seq.head.
scala> x = Array(1, 2, 3)
x: Array[Int] = [I#545e9f15
scala> x
res64: Array[Int] = Array(1, 2, 3)
scala> x(0)
res65: Int = 1
scala> x.head
res66: Int = 1
scala> x(0) += 1
scala> x.head += 1
<console>:13: error: value += is not a member of Int
x.head += 1
^
scala> x.head = 1
<console>:12: error: value head_= is not a member of scala.collection.mutable.ArrayOps[Int]
x.head = 1
^
scala>
Seems there's some implicit conventions happening beneath.
But from Scala API, the type of Array.head is Int (in my case):
def head: T
So why can I modify this element?

The key difference in your question is between x(0) += 1 (which works), and x.head += 1 (which fails).
x(0) += 1 is equivalent to x(0) = x(0) + 1, which is syntaxic sugar to x.update(0, x.apply(0) + 1). This instruction increases the value of x(0).
x.head returns and Int, which is immutable, thus x.head += 1 fails.

I believe this excerpt from the documentation might be useful:
val numbers = Array(1, 2, 3, 4)
val first = numbers(0)
numbers(3) = 100
Arrays make use of two common pieces of Scala syntactic sugar, shown
on lines 2 and 3 of the above example code. Line 2 is translated into
a call to apply(Int), while line 3 is translated into a call to
update(Int, T).
When you use parenthesis on the left side of assignments they are converted to an update call that replaces the element in the array. The .head method does not have such conversion, it is merely a way to get the first element so you can not use it to modify the list.

Related

Weird scala tuple behavior

I've notice this behavior in Scala
val list = List[(Int, Int)]()
val set = HashSet[(Int, Int)]()
scala> list :+ (1, 2)
res30: List[(Int, Int)] = List((1,2))
scala> list :+ (1 -> 2)
res31: List[(Int, Int)] = List((1,2))
scala> list :+ 1 -> 2
res32: List[(Int, Int)] = List((1,2))
//Work
// But the same for set not work
set += (1, 2)
<console>:14: error: type mismatch;
found : Int(2)
required: (Int, Int)
set += (1, 2)
//Ok may be += in set mean add all that mean this should work
set += ((1, 2))
set += ((1, 2), (3,4))
// Worked
// But why this one work
set += 1 -> 2
set += (1 -> 2)
set += ((1 -> 2))
Now I'm confuse, could you explain why tuple is not tuple?
scala> (4->5).getClass
res28: Class[_ <: (Int, Int)] = class scala.Tuple2
scala> (4,7).getClass
res29: Class[_ <: (Int, Int)] = class scala.Tuple2$mcII$sp
The parser stage -Xprint:parser gives
set.$plus$eq(1, 2)
which seems to resolve to
def += (elem1: A, elem2: A, elems: A*)
that is a method that accepts multiple arguments so compiler probably thinks elem1 = 1 or elem2 = 2 instead of considering (1,2) as a tuple.
missingfaktor points to SLS 6.12.3 Infix Operations as the explanation
The right-hand operand of a left-associative operator may consist of
several arguments enclosed in parentheses, e.g. 𝑒;op;(𝑒1,…,𝑒𝑛).
This expression is then interpreted as 𝑒.op(𝑒1,…,𝑒𝑛).
Now the operator += is left-associative because it does not end in :, and the right-hand operand of += consists of several arguments enclosed in parentheses (1,2). Therefore, by design, the compiler does not treat (1,2) as Tuple2.
I think the difference is that HashSet[T] defines two overloads for +=, one of which takes a single T, and the other takes multiple (as a T* params list). This is inherited from Growable[T], and shown here.
List[T].:+ can only take one T on the right hand side, which is why the compiler works out that it's looking at a tuple, not something that should be turned into a params list.
If you do set += ((1, 2)) then it compiles. Also, val tuple = (1,2); set += x works too.
See Mario Galic’s answer for why in the case of HashSet[T].+= the compiler chooses the overload that can't type over the one that can.

Scala: Indexed Seq instead of List in for loop

The following code shows a type mismatch error :
def f(arr:List[Int]): List[Int] =
for(num <- 0 to arr.length-1; if num % 2 == 1) yield arr(num)
It is says that it found an IndexedSeq instead of a List. The following works :
def f(arr:List[Int]): List[Int] =
for(num <- (0 to arr.length-1).toList; if num % 2 == 1) yield arr(num)
I have used i <- a to b in a for loop before but haven't seen this error before. Can someone please explain why the format i <- a to b cannot be used here ?
because 0 to arr.length-1 return type is: IndexedSeq[Int], so when execute for yield it also will yield result with IndexedSeq[Int] type.
The correct function define:
def f(arr:List[Int]):IndexedSeq[Int] = for( num <- 0 to arr.length-1 if num%2==1) yield arr(num)
And
for( num <- 0 to arr.length-1 if num%2==1) yield arr(num)
will translate to:
scala> def f(arr:List[Int]) = (0 to arr.length-1).filter(i => i%2==1).map(i => arr(i))
f: (arr: List[Int])scala.collection.immutable.IndexedSeq[Int]
So we can see the return type is decided by 0 to arr.length-1 type.
and (0 to arr.length-1).toList is changing the return IndexedSeq[int] type to List[Int] type, so for yield will generate result with type of List[Int].
In Scala, for each iteration of your for loop, yield generates a value which will be remembered. The type of the collection that is returned is the same type that you were iterating over, so a List yields a List, a IndexedSeq yields a IndexedSeq, and so on.
The type of (0 to arr.length-1) is scala.collection.immutable.Range, it's Inherited from scala.collection.immutable.IndexedSeq[Int]. So, in the first case, the result is IndexedSeq[Int], but the return type of function f is List[Int], obviously it doesn't work. In the second case, a List yields a List, and the return type of f is List[Int].
You can also write function f as follow:
def f(arr: List[Int]): IndexedSeq[Int] = for( a <- 1 to arr.length-1; if a % 2 == 1) yield arr(a)
Another example:
scala> for (i <- 1 to 5) yield i
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5)
scala> for (e <- Array(1, 2, 3, 4, 5)) yield e
res1: Array[Int] = Array(1, 2, 3, 4, 5)
In scala for is a syntax sugar, where:
for (i <- a to b) yield func(i)
translate to:
RichInt(a).to(b).map({ i => func(i) })
RichInt.to returns a Range
Range.map returns a IndexedSeq

How to easily convert IndexedSeq[Array[Int]] to Seq[Seq[Int]] in Scala?

I have a function that takes a list of lists of integer, specifically Seq[Seq[Int]]. Then I produce this data from reading a text file and using split, and that produces a list of Array. That is not recognized by Scala, who raises a match error. But either IndexedSeq or Array alone are OK with a Seq[Int] function, apparently only the nested collection is an issue. How can I convert implicitly IndexedSeq[Array[Int]] to Seq[Seq[Int]], or how else could I do this other than using toList as demonstrated below? Iterable[Iterable[Int]] seems to be fine, for instance, but I can't use this.
scala> def g(x:Seq[Int]) = x.sum
g: (x: Seq[Int])Int
scala> g("1 2 3".split(" ").map(_.toInt))
res6: Int = 6
scala> def f(x:Seq[Seq[Int]]) = x.map(_.sum).sum
f: (x: Seq[Seq[Int]])Int
scala> f(List("1 2 3", "3 4 5").map(_.split(" ").map(_.toInt)))
<console>:9: error: type mismatch;
found : List[Array[Int]]
required: Seq[Seq[Int]]
f(List("1 2 3", "3 4 5").map(_.split(" ").map(_.toInt)))
^
scala> f(List("1 2 3", "3 4 5").map(_.split(" ").map(_.toInt).toList))
res8: Int = 18
The problem is that Array does not implement SeqLike. Normally, implicit conversions to ArrayOps or WrappedArray defined in scala.predef allow to use array just like Seq. However, in your case array is 'hidden' from implicit conversions as a generic argument. One solution would be to hint compiler that you can apply an implicit conversion to the generic argument like this:
def f[C <% Seq[Int]](x:Seq[C]) = x.map(_.sum).sum
This is similar to Paul's response above. The problem is that view bounds are deprecated in Scala 2.11 and using deprecated language features is not a good idea. Luckily, view bounds can be rewritten as context bounds as follows:
def f[C](x:Seq[C])(implicit conv: C => Seq[Int]) = x.map(_.sum).sum
Now, this assumes that there is an implicit conversion from C to Seq[Int], which is indeed present in predef.
How about this:
implicit def _convert(b:List[Array[Int]]):Seq[Seq[Int]]=b.map(_.toList)
Redefine f to be a bit more flexible.
Since Traversable is a parent of List, Seq, Array, etc., f will be compatible with these containers if it based on Traversable. Traversable has sum, flatten, and map, and that is all that's needed.
What is tricky about this is that
def f(y:Traversable[Traversable[Int]]):Int = y.flatten.sum
is finicky and doesn't work on a y of type List[Array[Int]] although it will work on Array[List[Int]]
To make it less finicky, some type view bounds will work.
Initially, I replaced your sum of sums with a flatten/sum operation.
def f[Y<%Traversable[K],K<%Traversable[Int]](y:Y):Int=y.flatten.sum
I found this also seems to work but I did not test as much:
def f[Y <% Traversable[K], K <% Traversable[Int]](y:Y):Int=y.map(_.sum).sum
This <% syntax says Y is viewable as Traversable[K] for some type K that is viewable as a Traversable of Int.
Define some different containers, including the one you need:
scala> val myListOfArray = List(Array(1,2,3),Array(3,4,5))
val myListOfArray = List(Array(1,2,3),Array(3,4,5))
myListOfArray: List[Array[Int]] = List(Array(1, 2, 3), Array(3, 4, 5))
scala> val myArrayOfList = Array(List(1,2,3),List(3,4,5))
val myArrayOfList = Array(List(1,2,3),List(3,4,5))
myArrayOfList: Array[List[Int]] = Array(List(1, 2, 3), List(3, 4, 5))
scala> val myListOfList = List(List(1,2,3),List(3,4,5))
val myListOfList = List(List(1,2,3),List(3,4,5))
myListOfList: List[List[Int]] = List(List(1, 2, 3), List(3, 4, 5))
scala> val myListOfRange = List(1 to 3, 3 to 5)
val myListOfRange = List(1 to 3, 3 to 5)
myListOfRange: List[scala.collection.immutable.Range.Inclusive] = List(Range(1, 2, 3), Range(3, 4, 5))
Test:
scala> f(myListOfArray)
f(myListOfArray)
res24: Int = 18
scala> f(myArrayOfList)
f(myArrayOfList)
res25: Int = 18
scala> f(myListOfList)
f(myListOfList)
res26: Int = 18
scala> f(myListOfRange)
f(myListOfRange)
res28: Int = 18

Missing parameter type for expanded function in Scala ArrayBuffer

While trying to run the following snippet from Scala for the impatient:
val b = ArrayBuffer(1,7,2,9)
val bSorted = b.sorted(_ < _)
I get the following error:
error: missing parameter type for expanded function ((x$1, x$2) => x$1.$less(x$2))
val bSorted = b.sorted(_ < _)
Can somebody explain what might be going on here. Shouldn't the parameter type be inferred from the contents of the ArrayBuffer or do I need to specify it explicitly?
Thanks
.sorted takes an implicit parameter of type Ordering (similar to Java Comparator). For integers, the compiler will provide the correct instance for you:
scala> b.sorted
res0: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 7, 9)
If you want to pass a comparison function, use sortWith:
scala> b.sortWith( _ < _ )
res2: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 7, 9)
scala> b.sortWith( _ > _ )
res3: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(9, 7, 2, 1)
However, pay attention, although ArrayBuffer is mutable, both sort methods will return a copy which is sorted, but the original won't be touched.

Why += doesn't work with Lists?

Although I know that there are more idomatic ways of doing this, why doesn't this code work? (Mostly, why doesn't the first attempt at just x += 2 work.) Are these quite peculiar looking (for a newcomer to Scala at least) error messages some implicit def magic not working right?
scala> var x: List[Int] = List(1)
x: List[Int] = List(1)
scala> x += 2
<console>:7: error: type mismatch;
found : Int(2)
required: String
x += 2
^
scala> x += "2"
<console>:7: error: type mismatch;
found : java.lang.String
required: List[Int]
x += "2"
^
scala> x += List(2)
<console>:7: error: type mismatch;
found : List[Int]
required: String
x += List(2)
You're using the wrong operator.
To append to a collection you should use :+ and not +. This is because of problems caused when trying to mirror Java's behaviour with the use of + for concatenating to Strings.
scala> var x: List[Int] = List(1)
x: List[Int] = List(1)
scala> x :+= 2
scala> x
res1: List[Int] = List(1, 2)
You can also use +: if you want to prepend.
Have a look at the List in the Scala API. Methods for adding an element to a list are:
2 +: x
x :+ 2
2 :: x