如何模拟“一次分配” Scala 中的 var?

发布于 2024-10-06 22:02:28 字数 1054 浏览 8 评论 0原文

这是我之前的初始化变量问题。

假设我们正在处理此上下文:

object AppProperties {

   private var mgr: FileManager = _

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

}

此代码的问题是 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))
   }

}

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

This is a follow-up question to my previous initialization variable question.

Suppose we're dealing with this context:

object AppProperties {

   private var mgr: FileManager = _

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

}

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

}

... but this feels rather heavyweight to me (and initialization reminds me too much of C++ :-)). Any other idea?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

江心雾 2024-10-13 22:02:28

您可以使用隐式来做到这一点,使隐式仅在应该能够重新分配的方法中可用。查看值不需要隐式,因此“变量”对其他方法是可见的:

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

}

You could do it with implicits, making the implicit available only in the method that is supposed to be able to reassign. Viewing the value doesn't require the implicit, so the "variable" is visible to other methods:

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

}
独闯女儿国 2024-10-13 22:02:28

好的,这是我的建议,直接受到 的启发axel22 的,雷克斯·克尔 的,以及 Debilski< /a> 的答案:

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,该对象仅返回一次。尽管如此,只要调用者拥有原始凭证,就可以重新分配变量。这适用于 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
}

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

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

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

Typical usage would be as follows:

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
}

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.

海拔太高太耀眼 2024-10-13 22:02:28

我假设您不需要使用基元有效地执行此操作,并且为了简单起见,您也不需要存储 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....

I assume you don't need to do this efficiently with primitives, and for simplicity that you also don't need to store null (but you can of course modify the idea if these assumptions are false):

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
}

and just use this class wherever you need that functionality. (Maybe you would prefer apply() to get?)

If you really want it to look just like a variable (or method) access with no extra tricks, make the SetOnce private, and

private val unsetHolder = new SetOnce[String]
def unsetVar = unsetHolder.get
// Fill in unsetHolder somewhere private....
毁虫ゝ 2024-10-13 22:02:28

这并不是最好的方式,也不是您所要求的,但它为您提供了一些访问封装:

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

Not really the nicest way and not really what you asked for but it gives you some encapsulation of access:

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)
    }
  }
}
烂柯人 2024-10-13 22:02:28

看看JPP的帖子我有做了另一个变体:

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

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

Looking at JPP’s post I have made another variation:

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

This time, we are perfectly allowed to assign the var multiple times but we need to have the correct credential in scope. The credential is created and returned on first assign which is why we need to save it immediately to implicit val credential = mgr := new FileManager(config). If the credential is incorrect, it won’t work.

(Note that the implicit credential does not work if there are more credentials in scope because they’ll have the same type. It might be possible to work around this but I’m not sure at the moment.)

水晶透心 2024-10-13 22:02:28

我在想:

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

I was thinking something like:

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 can be set exactly once.

谁与争疯 2024-10-13 22:02:28

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

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

It’s not exactly the same thing but in many cases, the solution for this ‘set the variable once and then keep using it’ is simple subclassing with or without a special factory method.

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(...)
}
贵在坚持 2024-10-13 22:02:28

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

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

You can always move that value to another object, initialize it only once and access it when needed.

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
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文