Scala Traits – Rough notes...work in progress – Doomed useful soon, but not today



Scala Traits – Rough notes...work in progress – Doomed useful soon, but not today

0 0


scala-traits-slides

Slides about traits in Scala

On Github jaceklaskowski / scala-traits-slides

Scala Traits

Rough notes...work in progress

Doomed useful soon, but not today

Delivered by Jacek Laskowski / @jaceklaskowski

Friendly Heads-Up

This presentation is a mere summary a mere summary of what I could learn from the many sources available on the Internet. I wholeheartedly apologise for being incompetent to yield added value besides the presentation itself.

Programming in Scala, First Edition

Chapter 12 Traits

by Martin Odersky, Lex Spoon, and Bill Venners December 10, 2008http://www.artima.com/pins1ed/traits.html

Introduction

  • Traits are a fundamental unit of code reuse in Scala.
  • A trait encapsulates method and field definitions.
  • Reused by mixing them into classes
  • A class can mix in any number of traits
  • Two of the most common ways they are useful
    • widening thin interfaces to rich ones
    • defining stackable modifications

12.1 How traits work

package scala

// See http://www.scala-lang.org/api/current/#scala.Function1
trait Function1[-T1, +R] extends AnyRef {
  def apply(v1: T1): R
}
  • trait keyword defines a new trait
  • Function1 trait has the (implicit) default superclass of AnyRef
  • Can be mixed in using either extends or with keywords
class F_Int2Int extends Function1[Int, Int] {
  def apply(x: Int): Int = x + 1
}
  • F_Int2Int class implicitly inherit the trait's superclass - AnyRef

trait Function1

scala> val f = new Function1[Int, Int] {
     |   def apply(x: Int): Int = x + 1
     | }
f: Function1[Int,Int] = $anon$1@59020d40

scala> f(0)
res0: Int = 1

12.1 How traits work cont.

package scala

import scala.compat.Platform.currentTime
import scala.collection.mutable.ListBuffer

trait App extends DelayedInit {
  protected def args: Array[String] = ...
  def main(args: Array[String]) = { ... }
}
  • Implementation
object Main extends App {
  Console.println("Hello World: " + (args mkString ", "))
}

12.1 How traits work cont.

// from Akka's akka.actor package
trait ActorLogging { this: Actor ⇒
  private var _log: LoggingAdapter = _

  def log: LoggingAdapter = {
    // only used in Actor, i.e. thread safe
    if (_log eq null)
      _log = akka.event.Logging(context.system, this)
    _log
  }

}

Speakable trait

trait Speakable {
  def speak() = {
    println("I am indeed speaking")
  }
}
  • No explicit extends means AnyRef
  • A single definition of concrete method speak
scala> val s = new Speakable {}
s: Speakable = $anon$1@22f195ea

scala> s.speak
I am indeed speaking

Speakable trait cont.

class Human extends Speakable {
  override def speak() = {
    println("Hello, a Human speaking")
  }
}
  • Note override keyword to override default implementation of speak method
scala> val h = new Human
h: Human = Human@962345b

scala> h.speak
Hello, a Human speaking

Trait is type

  • A trait also defines a type
scala> val s: Speakable = new Human
s: Speakable = Human@65c7d581

scala> s.speak
Hello, a Human speaking
  • s could have been initialized with any object that has Speakable mixed in

Single extends, multiple with

  • Mind the singulars and plurals :-)
class Animal
trait CanWalk

class Human extends Animal with Speakable with CanWalk {
  override def speak() = {
    println("Hello, a Human speaking")
  }
}
  • Scala allows for at most one superclass and zero or many traits
  • When no superclass is explicitly defined use extends for the first trait
trait CanWalk
trait CanSing

class Living extends CanWalk with CanSing
  • What methods are part the class' API?

Spray-Swagger project

Spray-Swagger brings Swagger support for Spray APIs.https://github.com/gettyimages/spray-swagger

SwaggerHttpService

  • The SwaggerHttpService is a trait extending Spray's HttpService

Traits the very beginning

  • Let's start with
  • traits are used to define object types by specifying the signature of the supported methods.
  • Scala allows traits to be partially implemented; i.e. it is possible to define default implementations for some methods.
  • Traits can inherit from other traits or from classes.
  • Mixin composition in Scala occurs at compile time.

Traits

  • Traits are useful for mixing functionality into a class.
  • traits are used to define object types by specifying the signature of the supported methods.
  • Scala allows traits to be partially implemented; i.e. it is possible to define default implementations for some methods.
  • Traits can inherit from other traits or from classes.
  • Mixin composition in Scala occurs at compile time.

Trait inheritance vs self-types (1 of 2)

  • A cannot be mixed into a concrete class that does not also extend B, i.e. A uses-a B:
trait B
trait A { this: B => }
  • any (concrete or abstract) class mixing in A will also be mixing in B, i.e. A is-a B:
trait B
trait A extends B
  • Using type parameters within self-types legal
trait A[Self] {this: Self => }  // correct
trait A[Self] extends Self      // incorrect

Trait inheritance vs self-types (2 of 2)

  • Self types allow you to define cyclical dependencies
trait A { self: B => }
trait B { self: A => }
  • Inheritance using extends does not allow that
trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A
  • self-types as structural types, i.e. anything that mixes in Foo must implement a no-arg close method returning Unit.
    trait Foo {
     this => { def close: Unit }
    }

Cake pattern

trait AB {
  def fa: String
  def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1

Traits are stackable

  • super calls in a trait are dynamically bound (it is referring a class or trait mixed before current one)
trait A{
    def a = 1
}

trait X extends A{
    override def a = {
        println("X")
        super.a
    }
}  


trait Y extends A{
    override def a = {
        println("Y")
        super.a
    }
}

scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1

scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1

Traits vs abstract classes

  • Abstract classes can have constructor parameters as well as type parameters.
  • Traits can have only type parameters.
  • Abstract classes are fully interoperable with Java. You can call them from Java code without any wrappers.
  • Traits are fully interoperable only if they do not contain any implementation code
  • When you're doubt, use traits.
  • you cannot directly extend multiple abstract classes
  • you can mixin multiple traits into a class

Scala Language Specification

Chapter 5 Classes and Objects

Code extends Intro

trait SayHello {
  def hello(s: String): String
}
trait SayStarredHello extends SayHello {
  abstract override def hello(s: String): String = "***" + super.hello(s)
}
trait SayTildedHello extends SayHello {
  abstract override def hello(s: String): String = "~~~" + super.hello(s)
}
class ReallySayHello extends SayHello {
  def hello(s: String): String = "Hello, " + s
}

// What's the output?
(new ReallySayHello with SayStarredHello with SayTildedHello).hello("Jacek")

type Answer = Code

scala> (new ReallySayHello with SayStarredHello with SayTildedHello).hello("Jacek")
res0: String = ~~~***Hello, Jacek

Warm-up

  • You can define new types using a trait or class of objects or of a single object
  • The superclass constructor normally refers to a class which is not a trait.
    • When the list of parents starts with a trait reference, it's implicitly extended to include the trait's supertype until at least one constructor that does not take parameters is found.

Trait

  • A class to be added to other classes as a mixin
  • It has no constructor parameters
  • Initialized after the superclass of the trait is
  • Actual supertype of a trait
    • Gives the context for resolving a super reference
    • Depends on the type to which the trait is mixed-in
    • It is not statically known at the time the trait is defined
  • Java interfaces can appear as mixins

Trait definition

  • trait keyword followed by TraitDef
  • TraitDef ::= id [TypeParamClause] TraitTemplateOpt
  • TraitTemplateOpt ::= 'extends' TraitTemplate | [['extends'] TemplateBody]
  • Early field definitions

Trait's early definitions

  • Early definitions are evaluated in the order they are being defined before the superclass constructor of the template is called.
  • Particularly useful for traits, which do not have normal constructor parameters
trait Greeting {
  val name: String
  val msg = "How are you, "+name
}
class C extends {
  val name = "Bob"
} with Greeting {
  println(msg)
}
// What's the output of the following line?
new C
  • The field name is initialized before the constructor of Greeting is called

Exercise

  • Define a trait to be the property of being comparable to objects of some type.
  • Let the class contain the abstract method <.
  • Let the class contain default implementations of the other comparison operators <=, >, and >= (using <).

Solution

trait Comparable[T <: Comparable[T]] { self: T =>
  def < (that: T): Boolean
  def <=(that: T): Boolean = this < that || this == that
  def > (that: T): Boolean = that < this
  def >=(that: T): Boolean = that <= this
}

Exercise

  • Define an object or a class of objects with the Comparable just-defined trait mixed in.
  • Test it (using specs2 or ScalaTest testing libraries)!

Solution

class AA(val n: Int) extends Comparable[AA] {
  def < (that: AA) = n < that.n
}
scala> val one = new AA(1)
one: AA = AA@67a667d6

scala> val two = new AA(2)
two: AA = AA@20e164a1

scala> one < two
res5: Boolean = true

scala> two < one
res6: Boolean = false

abstract override

  • Modifier combination allowed for value members of traits only
  • Mechanism to statically refer to abstract methods by super reference in subtrait
    • the order of the modifiers does not matter
    • the same modifier must only occur once
  • super calls are dynamically bound
  • Described in abstract override section in Scala 2.11's language specification

Exercise

  • Define another trait that extends the Comparable trait to print what is compared.
  • Test it (using specs2 or ScalaTest testing libraries)!

Solution (FIXME)

trait PrintedComparable[T <: Comparable[T]] extends Comparable[T] { self: T =>
  abstract override def < (that: T) = {
    println(s"Executing $this < $that")
    super.<(that)
  }
}

abstract modifier

  • Redundant for traits
  • Only abstract classes and traits can have abstract members
  • Can also be used in conjunction with override for class member definitions

Javap

The Java Class File Disassembler

:javap in Scala REPL

  • Use :javap to learn about the nitty-gritty of bytecode generation and how it affects Java interop in Scala
scala> :help
...
:javap <path|class>      disassemble a file or class name

scala> :javap
:javap [-lcsvp] [path1 path2 ...]

scala> :javap -help
usage       :javap [opts] [path or class or -]...
-help       Prints this help message
-raw        Don't unmangle REPL names
-app        Show the DelayedInit body of Apps
-fun        Show anonfuns for class or Class#method
-verbose/-v Stack size, number of locals, method args
-private/-p Private classes and members
-package    Package-private classes and members
-protected  Protected classes and members
-public     Public classes and members
-l          Line and local variable tables
-c          Disassembled code
-s          Internal type signatures
-sysinfo    System info of class
-constants  Static final constants

Sample :javap session

scala> trait A
defined trait A

scala> :javap -public A
Compiled from "<console>"
public interface A {
}

:javap class

scala> class Person(name: String)
defined class Person
scala> :javap -public Person
Compiled from "<console>"
public class Person {
  public Person(java.lang.String);
}

:javap class with val

scala> class Person(val name: String)
defined class Person
scala> :javap -public Person
Compiled from "<console>"
public class Person {
  public java.lang.String name();
  public Person(java.lang.String);
}

:javap class with var

scala> class Person(var name: String)
defined class Person
scala> :javap -public Person
Compiled from "<console>"
public class Person {
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public Person(java.lang.String);
}

:javap case class

scala> case class Person(name: String)
defined class Person
scala> :javap -public Person
Compiled from "<console>"
public class Person implements scala.Product,scala.Serializable {
  public java.lang.String name();
  public Person copy(java.lang.String);
  public java.lang.String copy$default$1();
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public scala.collection.Iterator<java.lang.Object> productIterator();
  public boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public Person(java.lang.String);
}

Credits

The End

BY Jacek Laskowski / blog.jaceklaskowski.pl