Search code examples
scalasubclassprivatepath-dependent-type

Working around not being able to subclass a path dependent type


I am looking for a way to restrict invocations of certain objects. Given a transactional system which defines a reference type, a transaction type, and an identifier:

trait Sys[S <: Sys[S]] {
  type Ref[A]
  type Tx <: Txn[S]
  type ID
}

I would like to be able to mix in a trait that can be used to create mutable references:

trait Mutable[S <: Sys[S]] {
  def id: S#ID
  protected final def newRef[A](implicit tx: S#Tx): S#Ref[A] =
    tx.newRef[A](this)
}

And the reference factory is defined as part of the transaction:

trait Txn[S <: Sys[S]] {
  /* private[Mutable] */ def newRef[A](mut: Mutable[S]): S#Ref[A]
  def newID: S#ID
}

Now, the problem is, in the following structure I can create references with false contexts:

def newStruct[S <: Sys[S]](cross: Mutable[S])(implicit tx: S#Tx) = 
  new Mutable[S] {
    val id        = tx.newID
    val allowed   = newRef[Int]
    val forbidden = tx.newRef[Int](cross) // shouldn't compile...
  }

I would like to disallow the last call. Obviously, I cannot make newRef in Txn private to Mutable, because Mutable is not an enclosing class of Txn. I also would like not to use package privacy, as one can easily break into newRef by defining an object in that package.

Ideally I would like this:

trait Sys[S <: Sys[S]] { trait Mutable }

class MyStruct[S <: Sys[S]] extends S#Mutable { ... }

which would solve all the problems. But this is disallowed, since S in S#Mutable "is not a legal prefix for a constructor" in scalac's universe....

Thanks for suggestions!


Solution

  • You can put the definition of Txn in Mutable's companion object, then make it private to Mutable. Not sure if there are further implications, though.

    trait Sys[ S <: Sys[ S ]] {
       type Ref[ A ]
       type Tx <: Txn[ S ]
       type ID
    }
    
    object Mutable {
      trait Txn[ S <: Sys[ S ]] {
        private[Mutable] def newRef[ A ]( mut: Mutable[ S ]) : S#Ref[ A ]
        def newID : S#ID
      }
    }
    
    trait Mutable[ S <: Sys[ S ]] {
       def id: S#ID
       protected final def newRef[ A ]( implicit tx: S#Tx ) : S#Ref[ A ] =
          tx.newRef[ A ]( this )
    
    }
    
    // Or maybe you could declare type Txn in the package object...
    trait Txn[ S <: Sys[ S ]] extends Mutable.Txn[S]
    
    object Foo {
      def newStruct[ S <: Sys[ S ]]( cross: Mutable[ S ])( implicit tx: S#Tx ) = 
        new Mutable[ S ] {
          val id        = tx.newID
          val allowed   = newRef[ Int ]
          val forbidden = tx.newRef[ Int ]( cross ) // doesn't compile...
        }
    }