we will introduce the following topic based on the Implementing lists. they are
- List class principles.
- List constructions.
List class principles
first, the abstract List class definition.
package scala
abstract class List[T+] { // ...
}
and by default, the provided two case classes that has two case class implementation. they are :: (yes, you are not mistaken, in scala you can use some identified that you believe is not a identifier)... and a Nil class. the hierarchy is as follow.
// the class that we will examine is as follow.
// scala
// List[+T]
// << sealed abstract>>
// ^
// |
// +---------------+---------------------+
// | |
// scala scala
// ::[T] Nil
//<<final case>> <<case object>>
List are covaraince, so that
// List are covariance
val xs = List(1, 2, 3) // type of xs:List[Int]
var ys : List[Any] = xs // type List[Any]
and this is the implementation of the Nil object.
// the nil object
case object Nil extends List[Nothing] {
override def isEmpty = true
def head : Nothing = throw new NoSuchElementException("head of empty list")
def tail : Nothing = throw new NoSuchElementException("head of empty list")
}
since List is covaraince, so that means that Nil can be a subclass of other List objct.
then, let's check the :: class.
// -- the :: class
final case class :: [T] (hd : T, tl: List[T]) extends List[T] {
def head = hd
def tail = tl
override def isEmpty : Boolean = false
}
this can be simplified.
// it can be made simpler
final case class :: [T] (head : T, tail: List[T]) extends List[T] {
override def isEmpty : Boolean = false
}
the three methods that lay the basis of the all List operations are isEmpty, head, and tail.
//, isEmpty, head, and tail are the three operation that is the foundation to every other list operations
// e.g.
def length : Int = if (isEmpty) 0 else 1 + tail + tail.length
def drop(n : Int) : List[T] = if (isEmpty) Nil else if (n <= 0) this else tail.drop(n - 1)
def map[U](f : T => U) : List[U] = if (isEmpty) Nil else if(head) :: tail.map(f)
List Construction
let's first check the an examplesclass Fruit
class Apple extends Fruit
class Orange extends Fruit
val apples = new Apple : NIl // type List[Apple]
val fruits = new Orange :: apples // type List[Fruit]
how is that happening.
// how ?
// flexibility is from the :: methods.
def ::[U >: T] (x : U) : List[U] = new scala.::(x, this)
and this ist he ::: method.
// the ::: method
def :::[U >: T] (prefix : List[U])
if (prefix.isEmpty) this
else prefix.head :: prefix.tail ::: this
however, you have to be careful when you use the List class, e.g. suppose that we increment each of the element in a list.
// -- the ListBuffer class
// List operation, not tail-recursive
def incAll(xs : List[Int]) :: List[Int] = xs match {
case List() => LIst()
case x : xs1 => x + 1 :: incAll(xs1)
}
this is not effecient because it is not tail recursive, and it has limitation on the size of elements that it can process.
and you may revise the code with For expressoin, again it is not effecient.
// another not effecient way
var result = List[Int]()
for (x <- xs ) result = result ::: List(x + 1) // ::: time proportional to the length of the list.
result
the more effecient way is to use ListBuffer, the code below.
// effecient, and ListBuffer
val buf = new ListBuffer[Int]
for (x <- xs) buf += x + 1
buf.toList
List class practise
We may goes into the List class details. doing so we know some system design practises which can be very useful experience on making our own library code or design our own systems.
first, check the map method we have.
// Map method in List.
final override def map[U] (f : T => U) : List[U] = {
val b = new ListBuffer[U]
var these = this
while (!these.isEmpty) {
b += f(these.head)
these = these.tail
}
b.toList
}
and the real definition of the :: class has the following
// real definition of the :: class
final case class ::[U](hd : U, private[scala] var tl : List[U]) extends List[U] {
def head = hd
def tail = tl
override def isEmpty : Boolean = false
}
note on the above code
- tl is var
- private[scala] qualifier on tl
ListBuffer may have something similar to this:
final class ListBuffer[T] extends Buffer[T] {
private var start : List[T] = Nil
private var last0 : ::[T] = _
private var exported : Boolean = false
override def toList : List[T] = {
exported = !start.isEmpty
start
}
override def += (x: T) {
if (exported) copy()
if (start.isEmpty) {
last0 = new scala.::(x, Nil)
start = last0
} else {
val last1 = last0 // join the last0 and last1..
last0 = new scala::(x, Nil)
last1.tl = last0
}
}
// ...
}
toList is a very cheap and effecient.
Functional outside
This is yet another design principle, where a strategy that scala uses, trying to combine purity with effeciency by carefully delimiting the effects of impure operations.