Scala snippets 2: List symbol magic
The following snippets are available:
- Scala snippets 1: Folding
- Scala snippets 2: List symbol magic
- Scala snippets 3: Lists together with Map, flatmap, zip and reduce
- Scala snippets 4: Pimp my library pattern with type classes
In scala every symbol can be a function, so overloading operators (which isn’t really overloading, since operators are already methods) is very easy and is something which you see in many libraries. In this snippet we’ll just explore a couple of the overloaded methods that make working with lists much easiers.
So lets get started and look at the ++ operator. First, like we always do, lets create a list.
scala> val list = 0 until 10 toList
list: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> val list2 = 10 to 0 by -1 toList
list2: List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
And just walk through the operators from here: http://www.scala-lang.org/api/2.11.1/index.html#scala.collection.immutable.List
The first operator we’ll look at is ++. With this operator we can add two lists together, and return a new one:
scala> val list3 = list ++ list2
list3: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
scala> val list3 = list2 ++ list
list3: List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
Note that you don’t have to add the same types. Scala will automatically select the most relevant superclass.
scala> val list1 = 0 to 10 toList
list1: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> val list2 = 10 to 0 by -1 toList
list2: List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
scala> val list3 = list1.asInstanceOf[List[Double]]
list3: List[Double] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> list3 ++ list2
res4: List[AnyVal] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
As you can see AnyVal is the most common supertype of both Int and Double so that one gets selected.
Now that we’ve seen the ++ operator lets look at one almost the same the ++: operator.With this operator we have the same semantics as ++ but this time the type of the result is determined by the right operand instead of the left one:
scala> vector1
res14: Vector[Int] = Vector(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
scala> list1
res15: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> vector1 ++ list1
res16: scala.collection.immutable.Vector[Int] = Vector(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> vector1 ++: list1
res17: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
On to the next two operators :+ and +:. With these two operators we can append and prepend an element to a list:
scala> 999 +: list1
res27: List[Int] = List(999, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> list1 :+ 999
res28: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 999)
Simple right? Note that the : is always on the side of the list (target). The same goes for the ++: operator we saw earlier.
What more do we have? :: and :::. Both these operators add something to the beginning of a list. The :: operator adds a single element, and the ::: operator add a complete list. So basically they are the same as the +: and the ++ operator. The main change is that ++ and +: can be used with Traversable and ::: and :: can only be used with a list.
scala> 11 +: list1
res38: List[Int] = List(11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> list1
res39: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> 11 +: list1
res40: List[Int] = List(11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> list2
res41: List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
scala> list1 ::: list2
res43: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
And then we’ve pretty much covered all except :\ and :/. These functions allow you to fold (see here for more) an list. :\ folds from right to left and :/ folds from left to right.
scala> list1
res50: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> (1 /: list1)((r,i) => {println(i);i+r})
0
1
2
3
4
5
6
7
8
9
10
res51: Int = 56
scala> (list1 :\ 1)((i,r) => {println(i);i+r})
10
9
8
7
6
5
4
3
2
1
0
res52: Int = 56
As you can see the folding direction determines whether the elements are processed from beginning to end or backwards.
And that’s it for this small snippet.