如何在Scala中模拟“一次分配"变量? [英] How to simulate an “assign-once” var in Scala?
问题描述
这是对我先前的初始化变量问题的跟进问题.
假设我们正在处理这种情况:
Suppose we're dealing with this context:
object AppProperties {
private var mgr: FileManager = _
def init(config: Config) = {
mgr = makeFileManager(config)
}
}
此代码的问题是AppProperties
中的任何其他方法都可能会重新分配mgr
.是否有一种技术可以更好地封装mgr
,使其他方法感觉像val
一样?我已经考虑过类似的事情(受答案):
The problem with this code is that any other method in AppProperties
might reassign mgr
. Is there a technique to better encapsulate mgr
so that it feels like a val
for the other methods? I've thought about 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))
}
}
...但这对我来说是相当沉重的(初始化使我想起了太多的C ++ :-).还有其他想法吗?
... but this feels rather heavyweight to me (and initialization reminds me too much of C++ :-)). Any other idea?
推荐答案
好的,这是我的建议,直接受雷克斯·克尔(Rex Kerr)和
OK, so here's my proposal, directly inspired by axel22's, Rex Kerr's, and Debilski's answers:
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 .这适用于AnyVal
和AnyRef
.隐式转换使我可以在许多情况下直接使用变量名,如果这种方法不起作用,我可以通过添加()
来显式转换它.
We get compile-time safety that :=
is not called accidentally as we need the object's SetOnceCredential
, which is returned only once. Still, the var can be reassigned, provided the caller has the original credential. This works with AnyVal
s and AnyRef
s. The implicit conversion allows me to use the variable name directly in many circumstances, and if this doesn't work, I can explicitly convert it by appending ()
.
典型用法如下:
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
中一样),则不会产生编译时错误,但在运行时失败.
This doesn't yield a compile-time error if at some other point in the same file, I try getting another credential and reassigning the variable (as in calledAfterInit
), but fails at run-time.
这篇关于如何在Scala中模拟“一次分配"变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!