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

时间:2010-12-09 23:04:38

标签: scala initialization variable-assignment final

这是my previous initialization variable question的后续问题。

假设我们正在处理这个背景:

object AppProperties {

   private var mgr: FileManager = _

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

}

此代码存在的问题是AppProperties中的任何其他方法都可能会重新分配mgr。是否有一种技术可以更好地封装mgr,使其感觉像其他方法的val?我想过这样的事情(灵感来自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))
   }

}

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

8 个答案:

答案 0 :(得分: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())                                                                                                                                                                                             
  }                                                                                                                                                                                                            

}

答案 1 :(得分: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()
}

我们得到编译时的安全性:=没有被意外调用,因为我们需要对象的SetOnceCredential,它只返回一次。如果调用者具有原始凭证,仍然可以重新分配var 。这适用于AnyValAnyRef。隐式转换允许我在许多情况下直接使用变量名称,如果这不起作用,我可以通过附加()显式转换它。

典型用法如下:

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
}

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

答案 2 :(得分:2)

我假设您不需要使用基元有效地执行此操作,并且为了简单起见,您也不需要存储null(但如果这些假设为假,您当然可以修改该想法):

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
}

只需在需要该功能的地方使用此类。 (也许您更希望apply()get?)

如果你真的希望它看起来像一个变量(或方法)访问而没有额外的技巧,那么将SetOnce设为私有,并且

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

答案 3 :(得分:2)

这不是最好的方式而不是你要求的,但它给你一些访问的封装:

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

答案 4 :(得分:2)

关注JPP’s post我做了另一种变体:

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

这一次,我们完全允许多次分配var,但我们需要在范围内拥有正确的凭据。在第一次分配时创建并返回凭证,这就是我们需要立即将其保存到implicit val credential = mgr := new FileManager(config)的原因。如果凭证不正确,则无法使用。

(请注意,如果范围中有更多凭据,则隐式凭证不起作用,因为它们具有相同的类型。可能可以解决此问题但我目前还不确定。)

答案 5 :(得分:0)

我在想这样的事情:

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可以设置一次。

答案 6 :(得分:0)

这不完全相同,但在很多情况下,这个'设置变量一次然后继续使用'的解决方案是简单的子类化,有或没有特殊的工厂方法。

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

答案 7 :(得分:0)

您始终可以将该值移动到另一个对象,仅初始化一次并在需要时访问它。

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