Allowing modularly defined
folds (tree traversals) by choosing a
first-class extensible representation of
algebras.
Allowing modularly defined
folds (tree traversals) by choosing a
first-class extensible representation of
algebras.
Allowing modularly defined
unfolds (object definitions) by choosing a
first-class extensible representation of
coalgebras.
vs.
trait CoffeeMachine {
def makeCoffee: Coffee
}
trait Grinder {
def grind(beans: Beans): Powder
}
trait Steamer {
def steam: Steam
def foam(m: Milk): MilkFoam
}
trait WaterLineConnected { self: CoffeeMachine => }
trait EspressoMachine { self: CoffeeMachine with Compressor => }
new CoffeeMachine with Grinder with Steamer {}
val cm = new CoffeeMachine
cm.makeCoffee
cm extends(Grinder) extends(Steamer)
Static Modularity
Temporal Modularity
Interface
trait Counter {
def get: Int
def inc: Unit
}
Implementation
trait CounterImpl extends Counter {
var count: Int // state
def get: Int = count
def inc: Unit = { count += 1 }
}
Instantiation
def newCounter(init: Int): Counter =
new CounterImpl { var count = init }
val c = newCounter(0) // c0
c.get //=> 0
c.inc // c1
c.get //=> 1
c.inc // c2
// ...
trait Counter {
def get: Int
def inc: Unit
}
trait CounterF[State] {
def get: Int
def inc: State
}
Interface
Endofunctor
trait Counter {
def get: Int
def inc: Unit
}
trait CounterF[State] {
def get: Int
def inc: State
}
trait CounterImpl extends Counter {
var count: Int
def get: Int = count
def inc: Unit = { count += 1 }
}
val CounterImpl = count => new CounterF[Int] {
def get: Int = count
def inc: Int = count + 1
}
Interface
Endofunctor
Implementation
Coalgebra
trait Counter {
def get: Int
def inc: Unit
}
trait CounterF[State] {
def get: Int
def inc: State
}
trait CounterImpl extends Counter {
var count: Int
def get: Int = count
def inc: Unit = { count += 1 }
}
val CounterImpl = count => new CounterF[Int] {
def get: Int = count
def inc: Int = count + 1
}
def newCounter(init: Int): Counter =
new CounterImpl { var count = init }
def newCounter(init: Int): Fix[CounterF] =
unfold(CounterImpl, init)
Interface
Endofunctor
Implementation
Coalgebra
Instantiation
Unfolding
val obj: Fix[F] = unfold(impl: S => F[S], init: S)
fixed point
coalgebra
inital state
interface functor
Split object implementation into modular components.
Use late bound self-references to articulate dependencies.
Split object implementation into modular components.
Use late bound self-references to articulate dependencies.
trait OpenCoAlg[Self[_], Prov[_], S] {
def apply[T](priv: Lens[T, S]): CoAlg[Self, T] => CoAlg[Prov, T]
}
provided interface
required interface
late bound self-reference
type CoAlg[F[_], S] = S => F[S]
Split object implementation into modular components.
Use late bound self-references to articulate dependencies.
trait OpenCoAlg[Self[_], Prov[_], S] {
def apply[T](priv: Lens[T, S]): CoAlg[Self, T] => CoAlg[Prov, T]
}
provided interface
required interface
late bound self-reference
type CoAlg[F[_], S] = S => F[S]
type CompleteCoAlg[F[_], S] = OpenCoAlg[F, F, S]
trait SkipF[State] {
def skip: State
}
trait SkipF[State] {
def skip: State
}
val SkipImpl = new OpenCoAlg[CounterF, SkipF, Unit] {
def apply[S](priv: Lens[S, Unit]) = self => state => new SkipF[S] {
def skip = self.apply(self.apply(state).inc).inc
}
}
dependency on Counter
using it in the implementation
val CounterImpl = ...
val SkipImpl = ...
val both = compose(
CounterImpl,
SkipImpl)
val c0 = unfold(CounterImpl, 0)
val c1 = c0.out.inc
val c0 = unfold(CounterImpl, 0)
val c1 = c0.out.inc
val c2 = c1.extend(SkipImpl, ())
val c3 = c2.out.skip
The type changes: Fix[CounterF] to Fix[CounterF with SkipF]
unfold(co1, s1) extend(co2, s2)
is implemented by
unfold(compose(co1, co2), (s1, s2))
... defining static composition of coalgebra components:
1. by pointwise application with the tupled state,
2. composing the resulting interface implementations.
... implementing extend in terms of compose, thus
val cm1 = unfold( CoffeeMachine )
val cm2 = cm1.makeCoffee
val cm3 = cm2 extends(Grinder) extends(Steamer)
- Allow references to the extended base coalgebra,
imitating super-calls.
- Allow selective open-recursion by passing the current
as well as the late bound self-reference.
We have shown
- how to encode extensible objects in Scala.
It seems
- that object algebras can be dualized meaningfully.
In the future
- we would like to investigate the duality formally,
- improve the usability by optimizing performance and
syntactic overhead.
Thank you!