I would like to exploit Scala\'s type system to constrain operations in a system where there are versioned references to some values. This is all happening in some transacti
FYI, and to close the question, here is another idea that I like because the client code is fairly clutter free:
trait System[A <: Access[_]] {
def in[T](v: Version)(fun: A => T): T
}
trait Access[Repr] {
def version: Version
def meld[R[_]](v: Version)(fun: Repr => Ref[_, R]): R[this.type]
}
trait Version
trait Ref[A, Repr[_]] {
def sub[B](b: B): Repr[B]
}
object MyRef {
def apply[A <: MyAccess](implicit a: A): MyRef[A] = new Impl[A](a)
private class Impl[A](a: A) extends MyRef[A] {
def sub[B](b: B) = new Impl[B](b)
def schnuppi(implicit ev: A <:< MyAccess) = a.gagaism
}
}
trait MyRef[A] extends Ref[A, MyRef] {
// this is how we get MyAccess specific functionality
// in here without getting trapped in more type parameters
// in all the traits
def schnuppi(implicit ev: A <:< MyAccess): Int
}
trait MyAccess extends Access[MyAccess] {
var head: MyRef[this.type]
var tail: MyRef[this.type]
def gagaism: Int
}
def test(sys: System[MyAccess], v0: Version, v1: Version): Unit = {
val v2 = sys.in(v0) { a => a.tail = a.meld(v1)(_.head); a.version }
val a3 = sys.in(v2) { a => a }
val (v4, a4) = sys.in(v1) { a =>
a.head = a.head
println(a.head.schnuppi) // yes!
(a.version, a)
}
// a3.head = a4.head // forbidden
}
The following seems to work:
trait Version
trait Ctx[+V1 <: Version] {
type V = V1
}
type AnyCtx = Ctx[_ <: Version]
type AnyRf[T] = Ref[_ <: Version, T]
object Ref {
implicit def access[C <: AnyCtx, R, T](r: R)(
implicit c: C, view: R => Ref[C#V, T]): T = view(r).access(c)
implicit def substitute[C <: AnyCtx, T](r: AnyRf[T])(implicit c: C): Ref[C#V, T] =
r.substitute( c )
}
trait Ref[V1 <: Version, T] {
def access(implicit c: Ctx[V1]): T
def substitute[C <: AnyCtx](implicit c: C): Ref[C#V, T]
}
trait Factory {
def makeVar[C <: AnyCtx, T](init: T)(implicit c: C): Ref[C#V, T]
}
// def shouldCompile1(r: AnyRf[String])(implicit c: AnyCtx): String = r
def shouldCompile2(r: AnyRf[String])(implicit c: AnyCtx): String = {
val r1 = Ref.substitute(r)
r1.access(c)
}
// def shouldFail(r: AnyRf[String])(implicit c: AnyCtx): String = r.access(c)
So the follow-up questions are
Ctx
to achieve this. I hate that these type
parameters accumulate like rabbits in my code.shouldCompile1
doesn't compile
—can i get the implicits to work as planned?EDIT:
This is wrong, too. The variance annotation is wrong. Because now the following compiles although it shouldn't:
def versionStep(c: AnyCtx): AnyCtx = c // no importa
def shouldFail3[C <: AnyCtx](f: Factory, c: C): String = {
val r = f.makeVar("Hallo")(c)
val c2 = versionStep(c)
r.access(c2)
}