Outline

• Existential types Abstract type members in a nutshell
• Use cases
• Encapsulation
• Modularization
• Type families
• Summary

Expression

A problem well put is half-solved

John Dewey.

Modularization and generalization

• in practice, sub-problems are:
• rarely independent of each other
• often similar

Means of abstraction

• Define something in terms of some unknown other thing
``def identity[A](a: A): A``

value type
parameter
abstract member

Value parameters

``````class Disc(radius: Double) {

def area: Double = Pi * radius * radius

}``````
``def printArea(disc: Disc): Unit = println(disc.area)``
``````val disc = new Disc(123)
printArea(disc)``````

Scala square of abstraction (2)

value type
parameter `(x: Int)` `(f: Int => Int)`
abstract member

Abstract value members

``````abstract class Disc {

def radius: Double

def area: Double = Pi * radius * radius

}``````
``def printArea(disc: Disc): Unit = println(disc.area)``
``````val disc = new Disc { val radius = 123 }
printArea(disc)``````

Scala square of abstraction (3)

value type
parameter `(x: Int)` `(f: Int => Int)`
abstract member `def x: Int` `def f(y: Int): Int`

Parameters vs abstract members

• Parameters and abstract members are equivalent means of abstraction
• Parameters come more from the functional programming paradigm
• Abstract members come more from the object oriented programming paradigm

Type parameters

``````trait Invertible[A] {
def invert(a: A): A
}``````
``````def invertLaw[T](t: T)(implicit invertible: Invertible[T]): Unit =
assert(invertible.invert(invertible.invert(t)) == t)``````
``````implicit val invertibleInt: Invertible[Int] =
new Invertible[Int] { def invert(n: Int) = -n }

invertLaw(42)``````

Scala square of abstraction (4)

value type
parameter `(x: Int)` `(f: Int => Int)` `[A]` `[F[_]]`
abstract member `def x: Int` `def f(y: Int): Int`

Abstract type members

``````trait Invertible {
type A
def invert(a: A): A
}``````
``````def invertLaw[T](t: T)(implicit invertible: Invertible { type A = T }): Unit =
assert(invertible.invert(invertible.invert(t)) == t)``````
``````implicit val invertibleInt: Invertible { type A = Int } =
new Invertible { type A = Int; def invert(n: Int) = -n }

invertLaw(42)``````

Scala square of abstraction (5)

value type
parameter `(x: Int)` `(f: Int => Int)` `[A]` `[F[_]]`
abstract member `def x: Int` `def f(y: Int): Int` `type A` `type F[_]`

Scala interface for *nix files API

``````object UnixFiles {
def open(path: String): Int = …
def read(file: Int): String = …
def close(file: Int): Int = …
}``````
``````def readSomething(): Unit = {
UnixFiles.read(42) // I can forge a file descriptor!
}``````

Hiding the `File` type

``````trait UnixFiles {
type File
def open(path: String): File
def read(file: File): String
def close(file: File): Int
}``````
``````def readSomething(files: UnixFiles): Unit = {
files.read(42)
//         ^^
// Error: Type mismatch;
//  found: Int
//  required: files.File
}``````

Hiding the `File` type

``````def readSomething(files: UnixFiles): Unit = {
val file = files.open("/foo")
files.read(file) // I have no other choice than
// calling the `open` method to
// get a `File` instance
}``````

Why is it different from using an opaque type?

``````object UnixFiles {

sealed trait File
private case class FileImpl(value: Int) extends File

def open(path: String): File = {
…
FileImpl(…)
}

def read(file: File): String = file match {
case FileImpl(fileImpl) => …
}

}``````

Why is it different from using an opaque type?

• Casts everywhere
• Also “hidden” from the point of view of implementers!

Encapsulation – summary

• Abstract type members are an effective way to make a type opaque from the “outside”

When to use this technique?

• When you want to hide the actual implementation type of an abstraction

User interface `Component`

``````trait Component[State] {
def view(state: State): Html
}``````

`Counter` component

``````object Counter extends Component[Int] {

def view(state: Int): Html =
<span>value = { state }</span>

// … other methods defining how to update the state

}``````

`Container` component

``````class Container[State](child: Component[State]) extends Component[State] {

def view(state: State): Html =
<div class="container">{ child.view(state) }</div>

}``````

`Tabs` component

``````class Tabs(children: List[(String, Component[_]]))
extends Component[Tabs.State[_]] {

def view(state: Tabs.State[_]): Html = …

}

object Tabs {

case class State[S](child: Component[S], childState: S)

}``````

`Tabs` component implementation

``````class Tabs(children: List[(String, Component[_]]))
extends Component[Tabs.State[_]] {

def view(state: Tabs.State[_]): Html =
<div class="tabs">
<ul>{ for ((label, _) <- children) yield <li>{ label }</li> }</ul>
<div>
{ state.child.view(state.childState) }
</div>
</div>

}``````

`Tabs` component implementation

``````class Tabs(children: List[(String, Component[_]]))
extends Component[Tabs.State[_]] {

def view(state: Tabs.State[_]): Html =
<div class="tabs">
<ul>{ for ((label, _) <- children) yield <li>{ label }</li> }</ul>
<div>
{ state.child.view(state.childState) }
//                         ^^^^^^^^^^^^^^^^
// type mismatch;
//  found   : state.childState.type (with underlying type _\$1)
//  required: _\$1
</div>
</div>

}``````

`Tabs` component implementation

``````class Tabs(children: List[(String, Component[_]]))
extends Component[Tabs.State[_]] {

def view(state: Tabs.State[_]): Html = {

def helper[A](s: Tabs.State[A]): Html =
s.child.view(s.childState)

<div class="tabs">
<ul>{ for ((label, _) <- children) yield <li>{ label }</li> }</ul>
<div>{ helper(state) }</div>
</div>
}

}``````

Redesign `Component` with abstract type members

``````trait Component {

type State

def view(state: State): Html

}``````

`Counter` (again)

``````object Counter extends Component {

type State = Int

def view(state: State): Html = <span>value = { state }</span>

// … other methods defining how to update the state

}``````

`Container` (again)

``````trait Container extends Component {

val child: Component

type State = child.State

def view(state: State): Html =
<div class="container">{ child.view(state) }</div>

}``````

`Tabs` – state (1)

``````class Tabs(children: List[(String, Component])) extends Component {

case class State(child: Component)(childState: child.State)

}``````
• We can not use a case class to model the `State` because of SI-5712

`Tabs` – state (2)

``````class Tabs(children: List[(String, Component])) extends Component {

trait State {
val child: Component
def childState: child.State
}

}``````

`Tabs` – `view` implementation

``````class Tabs(children: List[(String, Component])) extends Component {

def view(state: State): Html =
<div class="tabs">
<ul>{ for ((label, _) <- children) yield <li>{ label }</li> }</ul>
<div>
{ state.child.view(state.childState) }
</div>
</div>

}``````

Modularization – summary

• Abstract members (both types and values) are useful to encapsulate module internals
• Path-dependent types are used to nest modules
• Don’t work with class constructors (see SI-5712)

When to use this technique?

• When you see lots of wildcard type parameters

Naive solution

``````trait Plot {
def setDataset(dataset: Dataset): Unit
def getDataset: Dataset
}

trait Dataset``````

Naive solution – `XYPlot`

``````trait XYPlot extends Plot {
private var _dataset: XYDataset = _
def setDataset(dataset: Dataset) = _dataset = dataset.asInstanceOf[XYDataset]
def getDataset = _dataset
}

case class XYDataset(series: List[Series]) extends Dataset
case class Series(values: List[(Double, Double)])``````

Solution with abstract type members

``````trait Plot {
type Dataset
def setDataset(dataset: Dataset): Unit
def getDataset: Dataset
}``````

`XYPlot` (again)

``````trait XYPlot extends Plot {

case class Dataset(series: List[Series])
case class Series(values: List[(Double, Double)])

private var _dataset: Dataset = _

def setDataset(dataset: Dataset) = _dataset = dataset
def getDataset = _dataset

}``````

Type families – summary

• Abstract type members can be refined along with the specialization of a class

When to use this technique?

• When you want to consistently specialize several types

Summary

• Existential types are hidden polymorphic types
• We can encode them in Scala using abstract type members
• They can be useful in several situations
• encapsulation
• modularization
• family polymorphism

Defining a language as a data type

``````sealed trait Expr
case class Lit(x: Double) extends Expr
case class Add(lhs: Expr, rhs: Expr) extends Expr``````
``val twoPlusThree = Add(Lit(2), Lit(3))``
• Values of type `Expr` are descriptions of additions

Evaluating programs

``````def eval(expr: Expr): Double =
expr match {
case Lit(x)        => x
case Add(lhs, rhs) => eval(lhs) + eval(rhs)
}``````

Pretty-printing programs

``````def show(expr: Expr): String =
expr match {
case Lit(x)        => x.toString
case Add(lhs, rhs) => print(lhs) ++ " + " ++ print(rhs)
}``````

Generalizing interpreters

``````def fold[A](lit: Double => A, add: (A, A) => A): Expr => A = {
case Lit(x)        => lit(x)
case Add(lhs, rhs) => add(lhs, rhs)
}``````
``````val eval = fold[Double](identity, _ + _)

val show = fold[String](_.toString, _ ++ " + " ++ _)``````
• Functions processing `Expr` values (“folds”) are interpretations of the language

Alternative encoding: language definition

``````trait ExprDsl {
type Expr
def lit(x: Double): Expr
def add(lhs: Expr, rhs: Expr): Expr
}``````
``````trait Program extends ExprDsl {
val twoPlusThree = add(lit(2), lit(3))
}``````

Alternative encoding: evaluation interpreter

``````trait ExprDsl {
type Expr
def lit(x: Double): Expr
def add(lhs: Expr, rhs: Expr): Expr
}``````
``````trait Eval extends ExprDsl {
type Expr = Double
def lit(x: Double) = x
def add(lhs: Double, rhs: Double) = lhs + rhs
}``````

Alternative encoding: interpreting programs

``````new Program with Eval {
println(twoPlusThree) // 5
}``````
``````new Program with Show {
println(twoPlusThree) // "2 + 3"
}``````

AlternativeFinally tagless encoding

See also [C. Hofer et al. 2008], [J. Carette et al. 2009] and [B. Oliveira 2012]

Finally tagless encoding – extensibility

``````trait MulDsl extends ExprDsl {
def mul(lhs: Expr, rhs: Expr)
}``````
``````trait MulEval extends Mul with Eval {
def mul(lhs: Double, rhs: Double) = lhs * rhs
}``````

Type parameters vs type members (1)

``````trait HttpEndpoints[Req, Resp, Headers, Url] {
…
}``````
``````trait Program[Req, Resp, Headers, Url]
extends HttpEndpoints[Req, Resp, Headers, Url] {
…
}``````

Type parameters vs type members (2)

``````trait HttpEndpoints {
type Req
type Resp
type Headers
type Url
…
}``````
``trait Program extends HttpEndpoints``

Type parameters vs type members (3)

``````trait HttpEndpoints {
type Req <: ReqLike
type Resp
type Headers
type Url
…
}``````
``trait Program extends HttpEndpoints``

Finally tagless encoding – summary

• Extensible way to define embedded languages
• Heavily relies on object-oriented features
• abstract methods and abstract type members

When to use this technique?

• When you want to separate description from (possibly multiple) interpretation(s)