如何在Scala中模拟"assign-once"var?

Jea*_*let 4 scala final initialization variable-assignment

这是我之前的初始化变量问题的后续问题.

假设我们正在处理这种情况:

object AppProperties {

   private var mgr: FileManager = _

   def init(config: Config) = {
     mgr = makeFileManager(config)
   }

}
Run Code Online (Sandbox Code Playgroud)

此代码的问题在于AppProperties可能重新分配任何其他方法mgr.是否有一种技术可以更好地封装,mgr以便感觉像val其他方法一样?我想过这样的事情(灵感来自这个答案):

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

}
Run Code Online (Sandbox Code Playgroud)

...但这对我来说感觉相当重要(初始化让我想起了太多的C++ :-)).还有其他想法吗?

axe*_*l22 7

您可以使用implicits进行此操作,使隐式仅在应该能够重新分配的方法中可用.查看该值不需要隐式,因此"变量"对其他方法可见:

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())                                                                                                                                                                                             
  }                                                                                                                                                                                                            

}
Run Code Online (Sandbox Code Playgroud)


Jea*_*let 4

好的,这是我的建议,直接受到axel22Rex KerrDebilski答案的启发:

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()
}
Run Code Online (Sandbox Code Playgroud)

我们获得了编译时安全性,:=不会意外调用,因为我们需要对象的SetOnceCredential,该对象仅返回一次。尽管如此,只要调用者拥有原始凭证,就可以重新分配变量。这适用于AnyVals 和AnyRefs。隐式转换允许我在许多情况下直接使用变量名,如果这不起作用,我可以通过附加().

典型用法如下:

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
}
Run Code Online (Sandbox Code Playgroud)

如果在同一文件中的其他某个点,我尝试获取另一个凭据并重新分配变量(如 中所示calledAfterInit),但在运行时失败,则这不会产生编译时错误。