简体   繁体   中英

Abstract private fields in Scala trait

I happened to find that it's not allowed to have abstract private fields in a trait, that is,

trait A1 {
    //private val a: Int         // Not allowed
    protected val b: Int         // OK
}

And it seems all right to do such thing to an abstract class, if private fields are constructor parameters, that is,

abstract class A2 (private val i: Int) // OK

So I guess that a trait doesn't have constructor parameters, so there's no way to initialize them, therefore no abstract private fields are allowed.

If they are "protected", then a subclass can initialize them using pre-initialized fields. This approach allows subclass to see these fields.

What if I just want to initialize them and hide them afterwards, as in the following example?

object holding {
    trait trick {
        protected val seed: Int                     // Can't be private
        final def magic: Int = seed + 123
    }

    trait new_trick extends trick {
        def new_magic: Int = magic + 456
        def the_seed: Int = seed                    // [1]
    }

    def play: new_trick = new { val seed = 1 } with new_trick 

    def show_seed(t: new_trick): Int = t.the_seed   // [2]
}

I don't want anyone to be able to see seed, that is, [2] (and so [1]) shouldn't be allowed. Is there a way for a trait to do that?


As @Randall and @pagoda_5b have pointed out, my question doesn't make much sense. But luckily @Régis and @axel22 have turned it into another interesting question and provided a pattern for solving it.

A simple way to keep a val private while allowing the sub-traits to initalize it would be to define it as private but initialize it with the value returned by another protected method. Then the sub-traits can define this protected method so as to change the initial value, but cannot access the value itself. So you would change this:

trait A {
  protected val foo: Bar
}

into:

trait A {
  private val foo: Bar = initFoo 
  protected def initFoo: Bar
}

Now, only trait A can access the val foo . Sub-traits can set the initial value of foo by definint initFoo , but cannot access foo itself:

trait B extends A {
  protected def initFoo: Bar = ???
}

Obviously, initFoo itself is still accessible by sub-traits. This is often not a problem if initFoo creates a new instance everytime (in other words, it is a factory), as we might only be interested in making the instance private to A without being concerned with having sub-traits being able to create new instances of Bar (irrespective of whether the new instances are equals to foo as per their equals method).

But if it is a concern (and it certainly is in your case as seed is fo type Int and thus what you want to hide is a value and not just a reference), we can use an additional trick to allow sub-traits to define initFoo but prevent them (and their sub-traits) from being able to call it. This trick is, let's face it, pretty awful for such a simple need, but it illustrates a nice pattern for advanced access control. Credits goes to the standard library authors for the idea (see http://www.scala-lang.org/api/current/index.html#scala.concurrent.CanAwait ).

trait A {
  // A "permit" to call fooInit. Only this instance can instantiate InitA
  abstract class InitA private[this]()
  // Unique "permit"
  private implicit def initA: InitA = null

  private def foo: Int = fooInit
  protected def fooInit( implicit init: InitA ): Int
}

trait B extends A {
  protected def fooInit( implicit init: InitA ): Int = 123
}

Now, if B tries to call initFoo , the compiler will complain that it could not find an implicit of type InitA (the unique such instance is A.initA and is only accesible to in A ) .

As I said, it's a bit awful and the package private solution given by axel22 is certainly a much easier alternative (although it won't prevent anyone from defining their sub-traits inside the same package as yours, thus defeating the access restriction).

The best you could do is to declare these package private - private[package_name] . This would allow you to extend and define the trait within the same package in which you are doing your implementation, but disallow clients to use it from other packages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM