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.
}