Below code adds element to res
list. My question is how scala internally translates .::
symbols?
Code snippet:
var res = List[(Int, Int)]()
res .::= (1, 2)
res .::= (3, 4)
res
output:
res56: List[(Int, Int)] = List((1,2),(3,4))
There are a few things going on in that snippet. Before diving into it let's talk about the difference between var
and val
. Namely, that a variable declared using the val
keyword is immutable, i.e. its value cannot be changed:
scala> val x = 1
x: Int = 1
scala> x = 2
<console>:13: error: reassignment to val
x = 2
^
On the other hand, var
keyword is used to declare a mutable variable, i.e. its value can be changed:
scala> var y = "bar"
y: String = bar
scala> y = "foo"
y: String = foo
What if we wanted to compute a new value of y
by appending to its current value?
scala> y = y + "bar"
y: String = foobar
Sure that works, but it turns out there's a shorthand for doing that:
scala> y += "bar"
scala> y
res10: String = foobar
By the way, in Scala, +
is just a name of a method, so y + "bar"
is the same as y.+("bar")
. Ugly, but valid. Similarly, y.+=("bar")
is also a valid replacement of y += "bar"
.
Great, let's remember that for later. Next, as others have already pointed out, ::
is just a method for prepending elements to a list (from Java it can be invoked as someList.$colon$colon(someElement)
). The important thing to note is that the ::
method returns a new list:
scala> var letters = List("b", "c")
letters: List[String] = List(b, c)
scala> letters.::("a")
res1: List[String] = List(a, b, c)
scala> letters
res2: List[String] = List(b, c)
What if we wanted to set letters
to the list which contains the letter "a"?
scala> letters = letters.::("a")
letters: List[String] = List(a, b, c)
Notice that this looks awfully similar to the previous example with strings. Does the shorthand work here too?
scala> letters ::= "a"
scala> letters
res6: List[String] = List(a, b, c)
Yes, it does. letters.::=("a")
works as well.
Now, let's break down the original snippet:
Step 1
Create a variable named res
and assign it an empty, immutable list. This empty list is intended to contain pairs of integers (Int, Int)
.
var res = List[(Int, Int)]()
Here's an alternative way of doing the same thing:
var res = List.empty[(Int, Int)]
(which, in my opinion, is a bit easier to read)
Step 2
Prepend a new element (1, 2)
to list res
and reassign the resulting list back to res
.
res .::= (1, 2)
Or, without spaces:
res.::=(1, 2)
Looks familiar? We could've also written it out as:
res = res.::(1, 2)
Step 3
Prepend (3, 4)
following the logic in step 2
Step 4
Print out current value of res
, which should be: List((3,4), (1,2))
Side note
Confusingly, the compiler is lenient enough to allow us to specify only a single set of parentheses when calling ::
, though we really ought to have two sets: one for the method invocation and another one for indicating a pair of integers. So, there happens to be yet another valid
way of writing the same thing res.::=((1, 2))
.
More generally:
scala> def first(p:(Int, Int)):Int = p._1
first: (p: (Int, Int))Int
scala> first(6,7)
res0: Int = 6
scala> first((6,7))
res1: Int = 6
scala> first(6 -> 7) //lolz! another one using implicit conversion
res2: Int = 6
The implicit conversion is ever-present since it's defined in Predef.ArrowAssoc
mind = blown
I also recommend taking a look at What are all the instances of syntactic sugar in Scala?
.
(dot) used for method invocation on instance of a class.
::
is a method defined on the List
::
is a method declared in the List
class which creates instance of scala.collection.immutable.::
class.
Notice that ::
is a method in List class and also ::
is a final class in the package scala.collection.immutable
Here is the implementation of the ::
function in the List
class
@SerialVersionUID(-6084104484083858598L) // value computed by serialver for 2.11.2, annotation added in 2.11.4
sealed abstract class List[+A] extends AbstractSeq[A]
with LinearSeq[A]
with Product
with GenericTraversableTemplate[A, List]
with LinearSeqOptimized[A, List[A]]
with Serializable {
override def companion: GenericCompanion[List] = List
import scala.collection.{Iterable, Traversable, Seq, IndexedSeq}
def isEmpty: Boolean
def head: A
def tail: List[A]
// New methods in List
/** Adds an element at the beginning of this list.
* @param x the element to prepend.
* @return a list which contains `x` as first element and
* which continues with this list.
*
* @usecase def ::(x: A): List[A]
* @inheritdoc
*
* Example:
* {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}}
*/
def ::[B >: A] (x: B): List[B] =
new scala.collection.immutable.::(x, this)
.....
}
Here is how scala.collection.immutable.::
is defined.
@SerialVersionUID(509929039250432923L) // value computed by serialver for 2.11.2, annotation added in 2.11.4
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
override def tail : List[B] = tl
override def isEmpty: Boolean = false
}
The output of
var res = List[(Int, Int)]()
res .::= (1, 2)
res .::= (3, 4)
res
should be
List((3,4), (1,2))
because the colon colon method :: adds an element to the front of a list.
The dot .
is totally optional in this case - this is just for specifically stating, that you are calling method ::
on the list object res
. This means that your code is equivalent to this one:
var res = List[(Int, Int)]()
res ::= (1, 2)
res ::= (3, 4)
res
Internally colon colon ::
is implemented like this:
/** Adds an element at the beginning of this list.
* @param x the element to prepend.
* @return a list which contains `x` as first element and
* which continues with this list.
*
* @usecase def ::(x: A): List[A]
* @inheritdoc
*
* Example:
* {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}}
*/
def ::[B >: A] (x: B): List[B] =
new scala.collection.immutable.::(x, this)
A new list is created (because of the immutability) with the argument as a first element and the current list content as the rest
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With