Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does dot colon colon (.::) meaning in scala

Tags:

scala

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))

like image 298
Rahul Sharma Avatar asked Nov 11 '16 19:11

Rahul Sharma


3 Answers

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?

like image 138
Andrey Avatar answered Oct 17 '22 20:10

Andrey


Just method invocation

. (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

Scala Standard library

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
}
like image 3
pamu Avatar answered Oct 17 '22 22:10

pamu


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

like image 1
Anton Avatar answered Oct 17 '22 20:10

Anton