Scala Lists are quite similar to arrays, which means all the elements of a list have the same type - but there are two important differences. First, lists are immutable, which means elements of a list cannot be changed by assignment. Second, lists represent a linked list whereas arrays are flat. The type of a list that has elements of type T is written as List[T].

eq tests identity (same object):

val a = List(1, 2, 3)
val b = List(1, 2, 3)
(a eq b) should be(res0)

== tests equality (same content):

val a = List(1, 2, 3)
val b = List(1, 2, 3)
(a == b) should be(res0)

Nil lists are identical, even of different types:

val a: List[String] = Nil
val b: List[Int] = Nil

(a == Nil) should be(res0)
(a eq Nil) should be(res1)

(b == Nil) should be(res2)
(b eq Nil) should be(res3)

(a == b) should be(res4)
(a eq b) should be(res5)

Lists can be easily created:

val a = List(1, 2, 3)
a should equal(List(res0, res1, res2))

Lists can be accessed via head, headOption and tail. Accessing a list via head is unsafe and may result in a IndexOutOfBoundsException.

val a = List(1, 2, 3)
a.headOption should equal(Some(res0))
a.tail should equal(List(res1, res2))

Lists can be accessed by position:

val a = List(1, 3, 5, 7, 9)
a(0) should equal(res0)
a(2) should equal(res1)
a(4) should equal(res2)

intercept[IndexOutOfBoundsException] {
  println(a(5))
}

Lists are immutable:

val a = List(1, 3, 5, 7, 9)
val b = a.filterNot(v โ‡’ v == 5) // remove where value is 5

a should equal(List(1, 3, 5, 7, 9))
b should equal(List(res0, res1, res2, res3))

Lists have many useful utility methods:

val a = List(1, 3, 5, 7, 9)

// get the length of the list
a.length should equal(res0)

// reverse the list
a.reverse should equal(res1)

// map a function to double the numbers over the list
a.map { v โ‡’
  v * 2
} should equal(res2)

// filter any values divisible by 3 in the list
a.filter { v โ‡’
  v % 3 == 0
} should equal(res3)

Functions over lists can use _ as shorthand:

val a = List(1, 2, 3)

a.map {
  _ * 2
} should equal(List(res0, res1, res2))

a.filter {
  _ % 2 == 0
} should equal(List(res3))

Functions over lists can use () instead of {}:

val a = List(1, 2, 3)
a.map(_ * 2) should equal(List(res0, res1, res2))
a.filter(_ % 2 != 0) should equal(List(res3, res4))

Lists can be reduced with a mathematical operation:

val a = List(1, 3, 5, 7)
a.reduceLeft(_ + _) should equal(res0)
a.reduceLeft(_ * _) should equal(res1)

foldLeft is like reduce, but with an explicit starting value:

val a = List(1, 3, 5, 7)
// NOTE: foldLeft uses a form called currying that we will explore later
a.foldLeft(0)(_ + _) should equal(res0)
a.foldLeft(10)(_ + _) should equal(res1)
a.foldLeft(1)(_ * _) should equal(res2)
a.foldLeft(0)(_ * _) should equal(res3)

You can create a list from a range:

val a = (1 to 5).toList
a should be(res0)

You can prepend elements to a List to get a new List:

val a = List(1, 3, 5, 7)

0 :: a should be(res0)

Lists can be concatened and Nil is an empty List:

val head = List(1, 3)
val tail = List(5, 7)

head ::: tail should be(res0)
head ::: Nil should be(res1)

Lists reuse their tails:

val d = Nil
val c = 3 :: d
val b = 2 :: c
val a = 1 :: b

a should be(List(res0, res1, res2))
a.tail should be(res3)
b.tail should be(res4)
c.tail should be(res5)