Как смоделировать переменную «назначить один раз» в Scala? - PullRequest
4 голосов
/ 10 декабря 2010

Это дополнительный вопрос к моему предыдущему вопросу о переменной инициализации .

Предположим, мы имеем дело с этим контекстом:

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 ++ :-)). Любая другая идея?

Ответы [ 8 ]

7 голосов
/ 10 декабря 2010

Вы можете сделать это с имплицитами, делая неявное доступным только в методе, который должен быть переназначен. Просмотр значения не требует неявного, поэтому «переменная» видна другим методам:

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

}
4 голосов
/ 10 декабря 2010

ОК, так вот мое предложение, прямо навеянное ответами 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, который возвращается только один раз.Тем не менее, var может быть переназначен при условии, что у вызывающей стороны есть исходные учетные данные.Это работает с AnyVal с и AnyRef с.Неявное преобразование позволяет мне использовать имя переменной напрямую во многих обстоятельствах, и если это не сработает, я могу явно преобразовать его, добавив ().

Типичное использование будет следующим:

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), но происходит сбой во время выполнения.

2 голосов
/ 10 декабря 2010

Глядя на сообщение 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

На этот раз нам вполне разрешено назначать переменную несколько раз, но нам нужно иметь правильные учетные данные вобъем.Учетные данные создаются и возвращаются при первом назначении, поэтому нам необходимо немедленно сохранить их в implicit val credential = mgr := new FileManager(config).Если учетные данные неверны, они не будут работать.

(Обратите внимание, что неявные учетные данные не работают, если в области действия имеется больше учетных данных, поскольку они будут иметь одинаковый тип. Возможно, можно обойти эту проблему.это, но я не уверен в данный момент.)

2 голосов
/ 10 декабря 2010

Не совсем хороший способ и не совсем то, что вы просили, но он дает вам некоторую инкапсуляцию доступа:

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)
    }
  }
}
2 голосов
/ 10 декабря 2010

Я предполагаю, что вам не нужно делать это эффективно с примитивами, и для простоты вам также не нужно хранить 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 голосов
/ 10 декабря 2010

Вы всегда можете переместить это значение в другой объект, инициализировать его только один раз и обращаться к нему при необходимости.

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
    }
}
0 голосов
/ 10 декабря 2010

Это не совсем то же самое, но во многих случаях решение для этого - установить переменную один раз, а затем продолжать ее использовать - это просто создать подклассы с использованием специального метода фабрики или без него.

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 голосов
/ 10 декабря 2010

Я думал что-то вроде:

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 может быть установлен ровно один раз.

...