2010-12-09 47 views
4

这是my previous initialization variable question的后续问题。如何在Scala中模拟“分配一次”变量?

假设我们正在处理此背景下:

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++ :-))。任何其他想法?

回答

4

好了,这里是我的建议,通过axel22的,Rex Kerr的直接启发,Debilski的答案:

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,它只返回一次。如果调用者具有原始凭证,仍然可以重新分配变量。这适用于AnyVal s和AnyRef s。隐式转换允许我在很多情况下直接使用变量名称,如果这不起作用,我可以通过附加()来明确转换它。

典型用法如下:

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),但在运行时失败。

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

} 
+0

有趣的方法。 – Debilski 2010-12-09 23:54:26

+0

聪明,我喜欢它! – 2010-12-10 08:19:40

+0

最后的解决办法张贴在这里:http://stackoverflow.com/questions/4404024/how-to-simulate-an-assign-once-var-in-scala/4407534#4407534 – 2010-12-10 09:55:21

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.... 
+0

不错的一个。我将结合你的答案和axel22的隐式赋值。 – 2010-12-10 08:18:31

+0

任何原因你默认`null:A`而不是`None:Option [A]`? – Debilski 2010-12-10 08:32:27

+0

我已经在这里发布了我的最终解决方案:http://stackoverflow.com/questions/4404024/how-to-simulate-an-assign-once-var-in-scala/4407534#4407534 – 2010-12-10 09:54:22

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

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) 
    } 
    } 
} 
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(...) 
} 
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 
    } 
} 
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)。如果凭证不正确,则不起作用。

(请注意,因为他们将有相同类型的隐含证书不一样,如果有在范围上更凭据工作。这也许可以解决这一点,但我不知道的时刻。)