Protecting a lazy value only from inside the class

Maybe someone has some clever way for me to do the following.

package MyLib
class Foo {
  lazy val dontTouchInside : Int = ???
}

Foo is library class that I’m exposing to users from which they can inherit.
The dontTouchInside lazy value must only be accessed post-construction of Foo or internally with [MyLib] privileges.

Is there some way I can enforce with the compiler’s help that new Foo{}.dontTouchInside works, but new Foo { dontTouchInside} fails?


A side note: the above would not be needed if Scala had some kind of onCreate capability.

The best I can come up with is:

package MyLib
class Foo {
  private[MyLib] lazy val dontTouchInside_ : Int = ???
  @inline def dontTouchInside : Int = dontTouchInside_
}

Is this equivalent?

1 Like

Can’t think of a way to enforce this as stated, but could you move dontTouchInside to a helper class?

package MyLib

class Foo {

** private[MyLib] def initDontTouchInsideFoo(helper: FooHelper): Int = ???**

}

class FooHelper(private val foo: Foo) {

** lazy val dontTouchInsideFoo: Int = foo.initDontTouchInsideFoo(this)**

}

Now you have to first construct Foo and then start constructing FooHelper to access dontTouchInsideFoo

I don’t see what this would accomplish vs having only the public lazy val?

Yes, you are right. Momentary relapse of scala-judgement :slightly_frowning_face:

Interesting, but not good for my use-case.

I found a workaround for my issue.
Library code

package MyLib
trait Post
@scala.annotation.implicitAmbiguous("This definition may be called only post-construction")
implicit object GeneralPost extends Post

class Foo {
  implicit object ForceAmbiguity extends Post
  private lazy val _postConstruction = {
    //Something to be used once at Post-Construction
    0
  }

  def postConstruction(implicit post : Post) : Int = _postConstruction
}

User code

new Foo {}.postConstruction //compiles
new Foo {postConstruction} //Error: This definition may be called only post-construction

So all public members that must be called post-construction must have a Post implicit (so the implicit also helps to document the constraint for the member).

Thank you all for your help.

3 Likes

The implementation above did not allow multiple definitions with the implicit Post calling one another. Here is a fixed implementation that still uses ambiguity slightly differently with the help of shapeless’s LowPriority:

Generic solution

trait PostConstruction
object PostConstruction {
  implicit def ev(implicit lp : shapeless.LowPriority) : PostConstruction = new PostConstruction {}
}

trait HasPostConstructionOnlyMembers {
  @scala.annotation.implicitAmbiguous("This is a post-construction definition only!")
  final protected implicit def __PostConstruction1(implicit lp : shapeless.LowPriority) : PostConstruction = new PostConstruction {}
  final protected implicit def __PostConstruction2(implicit lp : shapeless.LowPriority) : PostConstruction = new PostConstruction {}
}

Library

class DSLClass extends HasPostConstructionOnlyMembers  {
  private lazy val runOncePostConstruction : Int = 0 //Do something here
  protected def post1(implicit p : PostConstruction) : Int = runOncePostConstruction 
  def post2(implicit p : PostConstruction) : Int = post1
}

User code

new DSLClass{}.post2 //compiles
new DSLClass{post1} //error: This is a post-construction definition only!
new DSLClass{post2} //error: This is a post-construction definition only!