Marting Odersky in his book writes:
Class ::, pronounced “cons” for “construct,” represents non-empty lists.
and
The list construction methods :: and ::: are special. Because they end in a colon, they are bound to their right operand. That is, an operation such as x :: xs is treated as the method call xs.::(x), not x.::(xs). In fact, x.::(xs) would not make sense, as x is of the list element type, which can be arbitrary, so we cannot assume that this type would have a :: method. For this reason, the :: method should take an element value and yield a new list
So is ::
a method or a class?
It is both a class and a method. Cons is a type parametised class. List[A] has two sub classes: Cons and Nil. As Cons is a class it can be created by its constructor as follows:
val s = new ::[Int](4, Nil)
Cons is a case class and we use the constructor when we do a pattern match. Cons is also a method on the list class that is implemented in its two sub classes. Hence we can use the cons method on the cons class that we have created above.
val s1 = s.::(5)
Part of the confusion may arise, because we normally create Lists using the apply method of the List object:
val s2 = List(1, 2, 3)
Normally the apply method of an object returns a new instance of the Class with the same name as an object. This is however just convention. In this particular case the List Object returns a new instance of the Cons subclass. The List class itself is a sealed abstract class so can not be instantiated. So the above apply method does the following:
val s2 = 1 :: 2 :: 3 :: Nil
Any method that ends in a ':' is a method on the operand to its right so it can be rewritten as
val s2 = 1 :: (2 :: (3 :: Nil)) //or as
val s2 = Nil.::(3).::(2).::(1)
So the Cons(::) method on the Nil object takes the 3 as a parameter and produces an anonymous instantiation of the Cons class with 3 as its head and a reference to the Nil object as its tail. Lets call this anonymous object c1. The Cons method is then called on c1 taking 2 as its parameter returning a new anonymous Cons instantiation lets call it c2, which has 2 for its head and a reference to c1 as its tail. Then finally the cons method on the c2 object takes the 1 as a parameter and returns the named object s2 with 1 as its head and a reference to c2 as its tail.
A second point of confusion is that the REPL and Scala worksheets use a class' toString methods to display results. So the worksheet gives us:
val s3 = List(5, 6, 7) // s3 : List[Int] = List(5, 6, 7)
val s4 = List() // s4 : List[Nothing] = List()
val s5: List[Int] = List() // s5 : List[Int] = List()
s3.getClass.getName // res3: String = scala.collection.immutable.$colon$colon
s4.getClass.getName // res4: String = scala.collection.immutable.Nil$
s5.getClass.getName // res5: String = scala.collection.immutable.Nil$
As said above List is sealed so new sub sub classes can not be created as Nil is an object and Cons is final. As Nil is an object it can't be parametrised. Nil inherits from List[Nothing]. At first glance that doesn't sound that useful but remember these data structures are immutable, so we can never add to them directly and Nothing is a sub class of every other class. So we can add the Nil class (indirectly) to any List without problem. The Cons class has two members a head and another List. Its a rather neat solution when you clock it.
I'm not sure if this has any practical use but you can use Cons as a type:
var list: ::[Int] = List (1,2,3).asInstanceOf[::[Int]]
println("Initialised List")
val list1 = Nil
list = list1.asInstanceOf[::[Int]] //this will throw a java class cast exception at run time
println("Reset List")
Short answer: both.
There is a subclass of list called ::
, but you won't refer to it explicitly very often.
When you write e.g. 1 :: 2 :: Nil
, the ::
is a method on List
, which creates an instance of the class ::
behind the scenes.
::
is best thought of not as a method or a class, though, but as a constructor in the algebraic data type (ADT) sense. Wikipedia calls ADT constructors "quasi-functional entit[ies]", which makes them sound more complicated than they are, but isn't necessarily a bad way to think about them.
List
has two constructors, ::
(called cons in some languages) and Nil
. The Wikipedia article I've linked above gives a good introduction to the idea of lists as algebraic data types.
It's worth noting that in some languages (like Haskell) ADT constructors don't have their own types associated with them—they are just functions that create an instance of the ADT.
This is typically effectively the case in Scala as well, where it's fairly rare to refer to the type of an ADT constructor like ::
. It is possible, though—we can write this:
def foo(xs: ::[Int]) = ???
Or this (where Some
is one of the constructors for the Option
ADT).
def bar(s: Some[Int]) = ???
But this is in general not very useful, and could be considered an artifact of the way Scala implements algebraic data types.
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