如何在Scala中模拟“一次分配"变量? [英] How to simulate an “assign-once” var in Scala?

查看:60
本文介绍了如何在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 .这适用于AnyValAnyRef.隐式转换使我可以在许多情况下直接使用变量名,如果这种方法不起作用,我可以通过添加()来显式转换它.

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 AnyVals and AnyRefs. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆