Constraining an operation by matching a type parameter with an argument dependent parameter

I would like to use the Scala type system to restrict operations on a system where there are versions that reference some values. All this happens in some kind of transactional context of Ctx , to which version type V is attached. There is now Factory to create reference variables. They are created with the creation version attached to them (enter parameter V1 ) corresponding to the version of the context in which the factory was called.

Now imagine some code trying to access this link in a later version using a different Ctx . I want to make sure that you prohibit access to this type of Ref in any version ( Ctx V type) that does not correspond to the creation version, but it is allowed to allow the link some kind of replacement mechanism that returns a new kind of Ref , which can be accessed in the current version. (this is normal if substitute is called with an invalid context, for example older than Ref V1 - in this case, a runtime exception may be selected)

Here is my attempt:

 trait Version trait Ctx { type V <: Version } object Ref { implicit def access[C <: Ctx, R, T](r: R)(implicit c: C, view: R => Ref[C#V, T]): T = view(r).access(c) implicit def substitute[C <: Ctx, T](r: Ref[_ <: Version, T]) (implicit c: C): Ref[C#V, T] = r.substitute(c) } trait Ref[V1 <: Version, T] { def access(implicit c: { type V = V1 }): T // ??? def substitute[C <: Ctx](implicit c: C): Ref[C#V, T] } trait Factory { def makeRef[C <: Ctx, T](init: T)(implicit c: C): Ref[C#V, T] } 

And the problem is to define the access class method in such a way that the whole thing is compiled, i.e. the composite access object must compile, but at the same time I cannot call this method of the access class any Ctx , only with a version whose version corresponds to the reference version.

Preferably without structural typing or anything that poses performance problems.

+6
scala type-constraints type-parameter
source share
2 answers

FYI, and to close the question, here is another idea that I like, because the client code is pretty messy:

 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 } 
0
source share

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 following questions:

  • why do I need type redundancy for Ctx to achieve this. I hate that these types of parameters accumulate like rabbits in my code.
  • why shouldCompile1 not compile Can I make implicits work as planned?

EDIT:

This is also wrong. Deviation annotation is incorrect. Because now the following compilation, although this should not:

 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) } 
0
source share

All Articles