How to simulate "assign-once" var in Scala?

This is the next question to my previous question about variable initialization .

Suppose we are dealing with this context:

object AppProperties { private var mgr: FileManager = _ def init(config: Config) = { mgr = makeFileManager(config) } } 

The problem with this code is that any other method in AppProperties can reassign mgr . Is there a better way to encapsulate mgr so that it looks like val for other methods? I thought of something like this (inspired by this answer ):

 object AppProperties { private object mgr { private var isSet = false private var mgr: FileManager = _ def apply() = if (!isSet) throw new IllegalStateException else mgr def apply(m: FileManager) { if (isSet) throw new IllegalStateException else { isSet = true; mgr = m } } } def init(config: Config) = { mgr(makeFileManager(config)) } } 

... but for me it looks very hard (and initialization is too much like C ++ :-)). Any other idea?

+4
source share
8 answers

OK, so here is my suggestion directly inspired by axel22 , Rex Kerr , and Debilski, responding:

 class SetOnce[T] { private[this] var value: Option[T] = None def isSet = value.isDefined def ensureSet { if (value.isEmpty) throwISE("uninitialized value") } def apply() = { ensureSet; value.get } def :=(finalValue: T)(implicit credential: SetOnceCredential) { value = Some(finalValue) } def allowAssignment = { if (value.isDefined) throwISE("final value already set") else new SetOnceCredential } private def throwISE(msg: String) = throw new IllegalStateException(msg) @implicitNotFound(msg = "This value cannot be assigned without the proper credential token.") class SetOnceCredential private[SetOnce] } object SetOnce { implicit def unwrap[A](wrapped: SetOnce[A]): A = wrapped() } 

We get security at compile time, which := not called by accident, since we need a SetOnceCredential object that returns only once. However, var can be reassigned if the caller has the original credentials. This works with AnyVal and AnyRef s. Implicit conversion allows me to use the variable name directly in many cases, and if that doesn't work, I can explicitly convert it by adding () .

Typical uses would be:

 object AppProperties { private val mgr = new SetOnce[FileManager] private val mgr2 = new SetOnce[FileManager] val init /*(config: Config)*/ = { var inited = false (config: Config) => { if (inited) throw new IllegalStateException("AppProperties already initialized") implicit val mgrCredential = mgr.allowAssignment mgr := makeFileManager(config) mgr2 := makeFileManager(config) // does not compile inited = true } } def calledAfterInit { mgr2 := makeFileManager(config) // does not compile implicit val mgrCredential = mgr.allowAssignment // throws exception mgr := makeFileManager(config) // never reached } 

This does not give a compile-time error if, at some other point in the same file, I try to get another accounting parameter and reassign the variable (as in calledAfterInit ), but it does not work at runtime.

+4
source

You can do this with implicits, making it implicitly available only in a method that needs to be reassigned. Viewing a value does not require an implicit one, so the "variable" is visible to other methods:

 sealed trait Access trait Base { object mgr { private var i: Int = 0 def apply() = i def :=(nv: Int)(implicit access: Access) = i = nv } val init = { implicit val access = new Access {} () => { mgr := 5 } } } object Main extends Base { def main(args: Array[String]) { println(mgr()) init() println(mgr()) } } 
+7
source

I assume that you do not need to do this efficiently with primitives, and for simplicity you also do not need to store null (but you can of course change the idea if these assumptions are false):

 class SetOnce[A >: Null <: AnyRef] { private[this] var _a = null: A def set(a: A) { if (_a eq null) _a = a else throw new IllegalStateException } def get = if (_a eq null) throw new IllegalStateException else _a } 

and just use this class where you need this functionality. (Maybe you prefer apply() to get ?)

If you really want it to look the same as accessing a variable (or method) without additional tricks, make SetOnce private and

 private val unsetHolder = new SetOnce[String] def unsetVar = unsetHolder.get // Fill in unsetHolder somewhere private.... 
+2
source

Not the nicest way, not what you asked for, but it gives you some access encapsulation:

 object AppProperties { def mgr = _init.mgr def init(config: Config) = _init.apply(config) private object _init { var mgr: FileManager = _ def apply(config: Config) = { mgr = makeFileMaker(config) } } } 
+2
source

Looking at the JPPs post I made another variation:

 class SetOnce[T] { private[this] var value: Option[T] = None private[this] var key: Option[SetOnceCredential] = None def isSet = value.isDefined def ensureSet { if (value.isEmpty) throwISE("precondition violated: uninitialized value") } def apply() = value getOrElse throwISE("uninitialized value") def :=(finalValue: T)(implicit credential: SetOnceCredential = null): SetOnceCredential = { if (key != Option(credential)) throwISE("Wrong credential") else key = Some(new SetOnceCredential) value = Some(finalValue) key get } private def throwISE(msg: String) = throw new IllegalStateException(msg) class SetOnceCredential private[SetOnce] } private val mgr1 = new SetOnce[FileManager] private val mgr2 = new SetOnce[FileManager] val init /*(config: Config)*/ = { var inited = false (config: Config) => { if (inited) throw new IllegalStateException("AppProperties already initialized") implicit val credential1 = mgr1 := new FileManager(config) mgr1 := new FileManager(config) // works implicit val credential2 = mgr2 := new FileManager(config) // We get a new credential for this one mgr2 := new FileManager(config) // works inited = true } } init(new Config) mgr1 := new FileManager(new Config) // forbidden 

This time we are quite allowed to assign var several times, but we must have the correct credentials in the scope. Credentials are created and returned upon first assignment, so we must immediately save it until implicit val credential = mgr := new FileManager(config) . If the credentials are incorrect, they will not work.

(Note that implicit credentials do not work if there are more credentials in scope because they are of the same type. It is possible to get around this, but I'm not sure at the moment.)

+2
source

I thought something like:

 object AppProperties { var p : Int => Unit = { v : Int => p = { _ => throw new IllegalStateException } ; hiddenx = v } def x_=(v : Int) = p(v) def x = hiddenx private var hiddenx = 0 } 

X can be installed exactly once.

0
source

This is not exactly the same, but in many cases the solution for this "sets the variable once, and then continues to use it, it is simply subclassed using the special factory method or without it.

 abstract class AppPropertyBase { def mgr: FileManager } //.. somewhere else, early in the initialisation // but of course the assigning scope is no different from the accessing scope val AppProperties = new AppPropertyBase { def mgr = makeFileMaker(...) } 
0
source

You can always transfer this value to another object, initialize it only once and, if necessary, access it.

 object FileManager { private var fileManager : String = null def makeManager(initialValue : String ) : String = { if( fileManager == null ) { fileManager = initialValue; } return fileManager } def manager() : String = fileManager } object AppProperties { def init( config : String ) { val y = FileManager.makeManager( config ) // do something with ... } def other() { FileManager.makeManager( "x" ) FileManager.makeManager( "y" ) val y = FileManager.manager() // use initilized y print( y ) // the manager can't be modified } } object Main { def main( args : Array[String] ) { AppProperties.init("Hello") AppProperties.other } } 
0
source

All Articles