Samuel Tardieu @ rfc1149.net

Scala : Class 2

« Paradigmes et langages non classiques 2014

animals.scala

package fr.enst.plnc

// The Shout trait requires a name and gives a default
// version of the shout method.
trait Shout {
  def name: String
  def shout: String = s"$name"
}

// To be Furry, you need to be Shout.
trait Furry extends Shout {
  override def shout = s"comfortable ${super.shout}"
}

// Even a Furniture can Shout. It has a shared name for
// all its instances (unless you explicitly override it
// while creating the object).
class Furniture extends Shout {
  val name = "M"
  override def shout = "Möbel"
}

// A Trait can extends a class. It only means that it can
// only be used to extend this class or a descendant of this
// class.
trait Classy extends Animal {
  val x = 17
  def price: Int = 300
}

// Type linearization. We start from:
//   - class Chair extends Furniture with Furry
//   - class Furniture extends AnyRef with Shout
//   - class AnyRef extends Any
//   - trait Furry extends Shout
// Then:
//  1- Take your type and invert its definition except the first element
//     Chair : Furry : Furniture
//  2- Replace each item except the first by its linearization
//     Chair : (Furry : Shout) : (Furniture : Shout : AnyRef)
//  3- Append the standard classes AnyRef and Any
//     Chair : Furry : Shout : Furniture : Shout : AnyRef : Shout : AnyRef : Any
//  4- Remove the duplicates starting right
//     Chair   :   Furry   :  Furniture  :   Shout :  AnyRef  :    Any
//        -super->     -super->     -super->    -super->    -super>
// So, shout for Chair inherits the shout from Furry, which calls super.shout
// which is the should from Furniture.


class Chair extends Furniture with Furry {
  override val name = "chair"
}

class Animal(val name: String) extends Shout with Furry {
  override def shout = "Ouarf"
}

class Mammal(name: String) extends Animal(name) with Classy

class Human(val name: String) extends Shout {
  override def shout = "Yargla!"
}

class ClassRoom(val name: String)

// Any object can extend App and become a main program
object MyMainProgram extends App {

  val a = new Animal("Rex")
  println(a.shout)

  val h = new Human("Yves")
  println(h.shout)

  val c = new Chair
  println(c.shout)

  // We can create anonymous objects implementing a trait.
  // Here, this is similar to: new AnyRef with Shout { … }
  val anonymous = new Shout {
    val name = "anonymous"
  }
  println(anonymous.shout)

  // We can also have anonymous classes created on the fly.
  val cl = new ClassRoom("B312") with Shout
  print(cl.shout)
  val cl1: ClassRoom = cl
  val cl2: Shout = cl

}

imp.scala

package fr.enst.plnc

import scala.language.implicitConversions

class EnrichedString(s: String) {
  def double: String = s + s
}

object Imp extends App {

  // Those are three ways (the latter is the preferred one) to
  // implicitly convert a String into an enriched version with the
  // "double" method.

  // Method 1: a function to a new statically defined class.
  // implicit def convertToEnrichedString(s: String) = new EnrichedString(s)

  // Method 2: a function to a new anonymous class.
  // implicit def convertToEnrichedString(s: String) = new {
  // def double = s + s
  // }

  // Method 3: an implicit class.
  implicit class convertToEnrichedString(s: String) {
    def double = s + s
  }

  // This lets you extend foreign object, in the "Pimp my Library" style.
  implicit class xx(p: java.security.PublicKey) {
    def toHexadecimalString: String = ???  // ??? throws NotImplementedError
  }

  println("foobar".double)

// Implicit parameters allow passing useful data around.
//
//   def createTwoButtons(label1: String, label2: String)(implicit ctx: Context) {
//     …
//   }
//
//   createTwoButtons("b1", "b2")(mycontext)
//
//   {
//     implicit mycontext: Context = someContext()
//     createTwoButtons("b1", "b2")
//   }

  // We can manipulate functions…
  def callDouble(s: String)(f: String => Unit) = {
    f(s)
    f(s)
  }

  // … and call them in an elegant and intuitive way.
  callDouble("foobar") { s =>
    println(s)
  }

  // The underscore can be used in some cases as an automatically
  // named parameter.
  callDouble("xyzzy")(println(_))
  callDouble("xyzzy")(automaticallygeneratedname => println(automaticallygeneratedname))

  // Two underscores will get respectively the first and second
  // parameters.
  def f(x: Int) = x
  def g(x: Int) = x
  println((1 to 10).reduce(_ + _))
  println((1 to 10).reduce((a: Int, b: Int) => f(g(a)) + b))

  // collect allows for a combination of filter and map. Also, here
  // we demonstrate partial functions.
  println((1 to 10).collect {
    case x if x % 2 == 0 => x * 10
  })

  // Partial functions can be implicitly converted into normal functions.
  // If the matching is incomplete, MatchError can be thrown.
  callDouble("sssxyzzy") {
    case s if s.size == 5 => println(s)
    case _                => println("not the right size")
  }

  // Functions are covariant in their parameter and contravariant in
  // their result.
  //   trait Function[+P, -R] {
  //     def apply(p: P): R
  //   }
  //
  // It means that a function whose parameter is less constrained
  // (accepts more values) or whose result is more constrained
  // (returns less values) can be used in place of another function.

}