Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstract private fields in Scala trait

Tags:

scala

traits

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.

like image 568
cfchou Avatar asked Apr 05 '13 14:04

cfchou


People also ask

Can Scala traits have fields?

Traits can have methods(both abstract and non-abstract), and fields as its members.

What is difference between abstract class and trait in Scala?

The first difference was already mentioned: classes are limited to inherit from a single abstract class but can inherit from multiple traits. Another important difference is that abstract classes allow specifying constructor parameters. Traits do not allow us to do the same.

Can a trait extend an abstract class Scala?

Scala traits don't allow constructor parameters However, be aware that a class can extend only one abstract class.

What is abstract method in Scala?

Abstraction is the process to hide the internal details and showing only the functionality. In Scala, abstraction is achieved by using an abstract class. The working of the Scala abstract class is similar to Java abstract class. In Scala, an abstract class is constructed using the abstract keyword.


1 Answers

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).

like image 109
Régis Jean-Gilles Avatar answered Oct 06 '22 19:10

Régis Jean-Gilles