Constraining an operation by matching a type parameter to an argument's path-dependent type

后端 未结 2 1730
遇见更好的自我
遇见更好的自我 2021-01-05 14:04

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

相关标签:
2条回答
  • 2021-01-05 14:21

    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
    }
    
    0 讨论(0)
  • 2021-01-05 14:43

    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

    1. why I need a redundancy of the type parameter for Ctx to achieve this. I hate that these type parameters accumulate like rabbits in my code.
    2. why 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)
    }
    
    0 讨论(0)
提交回复
热议问题