Scala : Class 1

« Paradigmes et langages non classiques 2014

package fr.enst.plnc

// We define an Option abstract class, covariant in its type parameter.
// It means that if B derives from A, we will be able to assign an
// Option[B] to an Option[A].
// We add three methods. The third one is just because we love Haskell.

abstract class Option[+T] {
    def isDefined(): Boolean
    def get(): T
    // We could have used ">>=" as the method name as well.
    def bind[U](f: T => Option[U]): Option[U]
}

// Here we see that we can override a method by a val. Also, the "get" val
// is automatically built for a "case class" for all constructor parameters.
// Also, "apply" in the automatically created companion object allows
// us to build instances without writing "new", as in:
//   val s = Some("foobar")
// We also get a functional "toString", "equals", and "hashCode". Note that
// "==" calls "equals" in Scala.
case class Some[+T](get: T) extends Option[T] {
    val isDefined = true
    def bind[U](f: T => Option[U]) = f(get)
}

// A "case object" is a singleton of a case class, just as an object is
// a singleton of its own type. Also, we learn here that Nothing inherits
// from all types, which is practical. And no, we cannot create objects
// of type Nothing. Not even "null".
case object None extends Option[Nothing] {
    val isDefined = false
    def get = sys.error("no get for None")
    def bind[U](f: Nothing => Option[U]) = None
}

// Those test classes were made for, well, testing.
class A
class B extends A

// The list class is covariant in its type parameter.
abstract class List[+T] {
    def head: T
    def tail: List[T]
    def isEmpty: Boolean
    // Note that "::" is a perfectly valid method name. Since it ends with
    // ":", the method is looked up into its right argument parameter.
    def ::[U >: T](start: U): List[U] =
        Cons(start, this)
    // We need to use override when we override a concrete method. Note the
    // use of the string interpolator.
    override lazy val toString =
        if (isEmpty) "()" else s"(${internalToString})"

    // This is ugly. It was just to illustrate recursion from within the
    // string interpolator.
    def internalToString: String =
        if (isEmpty) ""
        else if (tail.isEmpty) s"$head"
        else s"$head, ${tail.internalToString}"
}

// Yet another case object!
case object Nil extends List[Nothing] {
    def head = sys.error("no head")
    def tail = sys.error("no tail")
    def isEmpty = true
}

// Yet another case class!
case class Cons[+T](head: T, tail: List[T]) extends List[T] {
    def isEmpty = false
}