we will introduce the following topic based on the Implementing lists. they are
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)
class 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 ::: thishowever, 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. resultthe 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
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
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.
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.