18. Advanced Types - RobertMakyla/scalaWiki GitHub Wiki

KEY POINTS: Singleton types are useful for method chaining and methods with object parameters. A type projection includes inner class instances for all objects of an outer class. A type alias gives a short name for a type. Structural types are equivalent to 'duck typing.' Existential types provide the formalism for wildcard parameters of generic types. Use a self type declaration to indicate that a trait requires another type. The 'cake pattern' uses self types to implement a dependency injection mechanism. An abstract type must be made concrete in a subclass. A higher-kinded type has a type parameter that is itself a parameterized type.

Singleton Types - used in Fluent Interface

 given any reference 'ref' I can call ref.type which has value: ref or null

 ref.type is a singleton type

 usage: Fluent interfaces:

     object Title
     object Price

     class Book (var title:Title, var price:Price){
         private var useNextArgAs: Any = null

         def set(obj: Title.type): this.type = { useNextArgAs = obj; this }     // param: object type
         def set(obj: Price.type): this.type = { useNextArgAs = obj; this }     // param: object type

         def to(arg: String) { if (useNextArgAs == Title) title = arg; if (useNextArgAs == Price) price = arg }
     }

     book.set(Title).to("Scala for the Impatient")
     book set Title to "Scala for the Impatient"      // fluent interface

Note: Unattended test - maybe this could be my multiplier - with fluent interface

Type Projections

     class A {
         class B
         def f(b: B) = println("Got my B!")
     }

     val a1 = new A
     val a2 = new A

     a2.f(a1.B)             // ERROR  type mismatch;
                            //        found   : a1.B
                            //        required: a2.B

 a1.B and a2.B are different classes !!!
 Now, # makes it possible for you to refer to such nested classes without restricting it to a particular instance

     A#B                                    // type projection - a B of any instance of A

     def g(b: A#B) = println("Got some B.")

     a2.g(a1.B)                             // no error with such function
                                            // --> Got some B.

*Compiler changes a1.B  to  a1.type#B    (B is a member of a1 singleton type)

 Caution:

     var a1 = new A
     val b1 = new a1.B     // error - myA not stable as it's var

Type Aliases

     class Book {
         type BookIndex = Map[String, (Int, Int)]  // 'BookIndex' is alias of Map[...]
     }

 Type Alias - must be nested in class or object

Structural Types !!!

     def appendLines(target: { def append(str: String): String }, lines: Iterable[String]) {
         for (l <- lines) { target.append(""+l+". "); target.append("\n") }
     }

 To call appendLines method, I must provide as 1st parameter an instance of any class that has append() method

 This is more flexible than defining a Appendable trait

Compound Types

 T1 with T2 with T3 ...

 In order to belong to the compound type, a value must belong to all of the individual types

 e.g.:

     val image = new Array[java.awt.Shape with java.io.Serializable]

 In order to put objects into this array, it must be BOTH Shape and Serializable

 Here, it must be Shape, Serializable and have contains method (Structural Type) :

     val image = new Array[Shape with Serializable { def contains(p: Point): Boolean } ]


Technically:
    T1                                 is shortcut from     T1 {}
    T1 with T2 with T3                 is shortcut from     T1 with T2 with T3 {}
    { def append(str: String): Any }   is shortcut from     AnyRef { def append(str: String): Any }

Infix Types

 It's a type with two type parameters, written in 'infix' syntax

     String Map Int                    is infix from        Map[String, Int]

 I can define mathematical notation (AxB)

     class MyOperator[T, U]

     new (Int MyOperator Int)                   is infix from        new MyOperator[Int,Int]

Existential Types

     Array[_ <: JComponent]    is the same as    Array[T] forSome {type T <: JComponent}

     Array[_]                  is the same as    Array[T] for some {type T}

     Map[_, _]                 is the same as    Map[T, U] for some {type T, type U}

Self Types

 Trait can be mixed only with subtypes of given type
 Class can be inherited only from subtypes of given type

     this: T  =>
     this: T with U with ... =>

 eg:
     trait LoggedException  {
         this: Exception =>
             def log() { getMessage() }
             // OK to call getMessage cause this is an Exception
     }

 But self types don't inherit automatically

     trait OtherException extends LoggedException { ... }
     // error that OtherException don't support Exception

 Need to repeat self type

     trait OtherException extends LoggedException {
         this: Exception =>
         ...
     }

Dependency Injection (in Java there is Spring for that)

 Eg: Switching between Mock database / Real database

     trait Logger { def log(msg: String) }       // logging

     trait Auth {                           // authentication needs logging
         this: Logger =>
             def login(id: String, password: String): Boolean
     }

     trait App {                            // app needs authentication
         this: Logger with Auth =>
             ...
     }

     object MyApp extends App with FileLogger("test.log") with MockAuth("users.txt")

 As it's a bit awkward, Better use CAKE PATTERN:

  You supply a component trait for each service that contains
  - Any dependent components, expressed as self types
  - A trait describing the service interface
  - An abstract val that will be instantiated with an instance of the service
  - Optionally, implementations of the service interface


     trait LoggerComponent {             // logging component
         trait Logger { }
         val logger: Logger

         class FileLogger(file: String) extends Logger { }
         class ConsoleLogger(file: String) extends Logger { }
     }

     trait AuthComponent {
         this: LoggerComponent =>       // access to logger component

         trait Auth { }
         val auth: Auth

         class ActiveDirectoryAuth(file: String) extends Auth { }
         class MockAuth(file: String) extends Auth { }
     }

     object AppComponents extends LoggerComponent with AuthComponent {
         val logger = new FileLogger("test.log")
         val auth = new MockAuth("users.txt")
     }

 It's better than Spring (XML config) - here compiler verifies if dependencies are ok

Abstract Types

 A class or trait can define an abstract type that becomes concrete in a subclass

     trait Reader {
         type Contents                            //abstract type
         def read(fileName: String): Contents
     }

     class StringReader extends Reader {
         type Contents = String
         def read(fileName: String) = Source.fromFile(fileName, "UTF-8").mkString
     }

     class ImageReader extends Reader {
         type Contents = BufferedImage
         def read(fileName: String) = ImageIO.read(new File(fileName))
     }

 It could be done also with type parameters (generic types) :

     trait Reader[C] {
         def read(fileName: String): C
     }

     class StringReader extends Reader[String] {
         def read(fileName: String) = Source.fromFile(fileName, "UTF-8").mkString
     }

     class ImageReader extends Reader[BufferedImage] {
         def read(fileName: String) = ImageIO.read(new File(fileName))
     }

 Use abstract types when the types are expected to be supplied in a subclass (our case)
 Use type parameters when the types are supplied as the class is instantiated

Family Polymorphism

 Java approach (Observer design pattern)

     trait Listener[E] {                                 // listener on E (event)
        def occurred(e: E): Unit
     }

     trait Source[E, L <: Listener[E]] {                 // collection of listeners
        private val listeners = new ArrayBuffer[L]
        def add(l: L) { listeners += l }
        def fire(e: E) {
          for (l <- listeners) l.occurred(e)
        }
     }

     trait ActionListener extends Listener[ActionEvent]  // listener on ActionEvent

     class Button extends Source[ActionEvent, ActionListener] {  // application launcher
        def click() {
          fire(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"))
        }
     }

 Scala approach:


     trait ListenerSupport {
        type S <: Source                  // abstract types
        type E <: Event
        type L <: Listener

        trait Event {
          var source: S = _
        }

        trait Listener {
          def occurred(e: E): Unit
        }

        trait Source {
          this: S =>
          private val listeners = new ArrayBuffer[L]
          def add(l: L) { listeners += l }
          def remove(l: L) { listeners -= l }
          def fire(e: E) {
              e.source = this
              for (l <- listeners) l.occurred(e)
          }
        }
      }


      object ButtonModule extends ListenerSupport {
         type S = Button                             // declaring abstract types
         type E = ButtonEvent
         type L = ButtonListener

         class ButtonEvent extends Event
         trait ButtonListener extends Listener
         class Button extends Source {
            def click() { fire(new ButtonEvent) }
         }
      }

Higher-Kinded Types

 when 'List' depends on a type T, this fact produces another type 'List[T]'

 We say that List is a type constructor

 How about taking it to another level?

 trait Iterable[E, C[_]] {         // higher kinded type takes, C depending on any type
     def iterator(): Iterator[E]
     def build[F](): C[F]
     def map[F](f : (E) => F) : C[F]
 }

 class MyIterator extends Iterable[Int, ArrayList]