Julien Richard-Foy
Scala Days – June 1st, 2017
http://julienrf.github.io/2017/existential-types
A problem well put is half-solved
John Dewey.
def identity[A](a: A): A
value | type | |
---|---|---|
parameter | ||
abstract member |
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)
value | type | |
---|---|---|
parameter | (x: Int) (f: Int => Int) |
|
abstract member |
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)
value | type | |
---|---|---|
parameter | (x: Int) (f: Int => Int) |
|
abstract member | def x: Int def f(y: Int): Int |
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)
value | type | |
---|---|---|
parameter | (x: Int) (f: Int => Int) |
[A] [F[_]] |
abstract member | def x: Int def f(y: Int): Int |
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)
value | type | |
---|---|---|
parameter | (x: Int) (f: Int => Int) |
[A] [F[_]] |
abstract member | def x: Int def f(y: Int): Int |
type A type F[_] |
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!
}
File
typetrait 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
}
File
typedef 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
}
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) => …
}
}
Component
trait Component[State] {
def view(state: State): Html
}
Counter
componentobject Counter extends Component[Int] {
def view(state: Int): Html =
<span>value = { state }</span>
// … other methods defining how to update the state
}
Container
componentclass Container[State](child: Component[State]) extends Component[State] {
def view(state: State): Html =
<div class="container">{ child.view(state) }</div>
}
Tabs
component
Tabs
componentclass 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 implementationclass 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 implementationclass 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 implementationclass 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>
}
}
Component
with abstract type memberstrait 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)
}
State
because of SI-5712Tabs
– state (2)class Tabs(children: List[(String, Component])) extends Component {
trait State {
val child: Component
def childState: child.State
}
}
Tabs
– view
implementationclass 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>
}
trait Plot {
def setDataset(dataset: Dataset): Unit
def getDataset: Dataset
}
trait Dataset
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)])
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
}
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))
Expr
are descriptions of additionsdef eval(expr: Expr): Double =
expr match {
case Lit(x) => x
case Add(lhs, rhs) => eval(lhs) + eval(rhs)
}
def show(expr: Expr): String =
expr match {
case Lit(x) => x.toString
case Add(lhs, rhs) => print(lhs) ++ " + " ++ print(rhs)
}
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, _ ++ " + " ++ _)
Expr
values (“folds”) are interpretations of the languagetrait 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))
}
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
}
new Program with Eval {
println(twoPlusThree) // 5
}
new Program with Show {
println(twoPlusThree) // "2 + 3"
}
See also [C. Hofer et al. 2008], [J. Carette et al. 2009] and [B. Oliveira 2012]
trait MulDsl extends ExprDsl {
def mul(lhs: Expr, rhs: Expr)
}
trait MulEval extends Mul with Eval {
def mul(lhs: Double, rhs: Double) = lhs * rhs
}
trait HttpEndpoints[Req, Resp, Headers, Url] {
…
}
trait Program[Req, Resp, Headers, Url]
extends HttpEndpoints[Req, Resp, Headers, Url] {
…
}
trait HttpEndpoints {
type Req
type Resp
type Headers
type Url
…
}
trait Program extends HttpEndpoints
trait HttpEndpoints {
type Req <: ReqLike
type Resp
type Headers
type Url
…
}
trait Program extends HttpEndpoints