2010-12-09 7 views
4

Dies ist eine Folgefrage an my previous initialization variable question.Wie simuliert man eine "Assign-Once" -Var in Scala?

Angenommen wir mit diesem Kontext zu tun hat:

object AppProperties { 

    private var mgr: FileManager = _ 

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

} 

Das Problem mit diesem Code ist, dass jedes andere Verfahren in AppPropertiesmgr zuweisen könnte. Gibt es eine Technik, um mgr besser zu verkapseln, so dass es sich wie ein val für die anderen Methoden anfühlt? Ich habe wie diese über etwas gedacht (inspiriert von 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)) 
    } 

} 

... aber das fühlt sich eher im Schwergewicht zu mir (und Initialisierung erinnert mich zu sehr an C++ :-)). Irgendeine andere Idee?

Antwort

4

OK, also hier ist mein Vorschlag, inspiriert direkt von axel22 's, Rex Kerr' s, und Debilski ‚s Antworten:

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

Wir erstellen Zeit bekommen Sicherheit, dass := nicht versehentlich aufgerufen wird wie wir brauchen das Objekt SetOnceCredential, die nur einmal zurückgegeben wird. Dennoch kann die Variable neu zugewiesen werden, sofern der Anrufer über die ursprünglichen Anmeldeinformationen verfügt.Dies funktioniert mit AnyVal s und AnyRef s. Die implizite Konvertierung ermöglicht es mir, den Variablennamen in vielen Fällen direkt zu verwenden, und wenn dies nicht funktioniert, kann ich ihn explizit durch Anhängen von () konvertieren.

Typische Anwendungen würden wie folgt aussehen:

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 
} 

Dies ergibt keine Fehler bei der Kompilierung, wenn an anderer Stelle in der gleichen Datei, versuche ich eine anderen Berechtigungsnachweis und die Neuzuweisung der Variable bekommen (wie in calledAfterInit), schlägt aber zur Laufzeit fehl.

+1

Aber wie ich es jetzt sehe, könnten Sie immer die 'SetOne.allowMe' Credential irgendwo in Ihrem Code (im Vergleich zu der' versiegelten' Version) abrufen und Zuordnung würde funktionieren solange es die erste Aufgabe ist. Im Grunde genommen ist der implizite Berechtigungsnachweis jetzt nutzlos. Oder fehlt mir etwas? – Debilski

+0

Zuordnung funktioniert nicht von überall in meinem Code, da 'mgr' privat ist, aber Sie haben Recht, das ist ein kleiner Nachteil. Aber ist das wirklich schlimmer als die Version von axel22, wo ich einen anderen 'Access' irgendwo in der gleichen Datei erstellen könnte? (Ich könnte 'Base' in eine andere Datei verschieben, um die 100% -Garantie zu bekommen, aber wäre das nicht nur für die Initialisierung eines Feldes zu viel? Ich würde es lieber in der gleichen Datei behalten und einen besseren Überblick bekommen) . Zweitens bevorzuge ich die leicht wiederzuverwendende 'SetOnce' im Gegensatz zu einer ausführlicheren Lösung, die es erfordern würde, dass ich neue Zugriffsmerkmale für jedes neue Set-once var ... –

+0

Es ist nicht besser oder schlechter, es ist nur, dass die Anmeldeinformationen nutzlos sind Ihr Fall, weil Sie 'SetOnce.allowMe' von überall in Ihrem Code aufrufen können und somit die Berechtigungsvariable erhalten. Du gewinnst dadurch eigentlich nichts. – Debilski

7

Sie könnten dies mit implicits tun, wodurch das implizite nur in der Methode zur Verfügung steht, die neu zugewiesen werden soll. den Wert Anzeigen erfordert nicht die impliziten, also die „Variable“ zu anderen Methoden sichtbar ist:

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

Interessanter Ansatz. – Debilski

+0

Smart, ich mag es! –

+0

Endgültige Lösung hier gepostet: http://StackOverflow.com/Questions/4404024/How-to-simulate-an-assign-once-var-in-scala/4407534#4407534 –

2

Ich nehme an, Sie dies mit Primitiven nicht effizient tun müssen, und der Einfachheit halber, die Sie auch don‘ t müssen null speichern (aber Sie können die Idee natürlich ändern, wenn diese Annahmen falsch sind):

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 
} 

und nur diese Klasse verwenden, wo immer Sie diese Funktionalität benötigen. (Vielleicht möchten Sie apply() zu get bevorzugen?)

Wenn Sie es wirklich wollen einfach nur, um wie eine Variable (oder Methode) Zugang ohne zusätzliche Tricks, machen die SetOnce privat und

private val unsetHolder = new SetOnce[String] 
def unsetVar = unsetHolder.get 
// Fill in unsetHolder somewhere private.... 
+0

Nice one. Ich verwende eine Kombination aus Ihrer Antwort und axel22 implizit für die Zuordnung. –

+0

Gibt es irgendeinen Grund, warum Sie sich auf "null: A" und nicht auf "None: Option [A]" festlegen? – Debilski

+0

Ich habe meine endgültige Lösung hier veröffentlicht: http://stackoverflow.com/questions/4404024/how-to-simulate-an-assign-once-var-in-scala/4407534#4407534 –

0

Ich war etwas denken wie:

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 kann genau einmal eingestellt werden.

+0

Danke, aber kaum besser als mein ursprünglicher Code, da andere Methoden in AppProperties möglicherweise noch HiddenX zuweisen. –

2

Nicht wirklich die schönste Art und nicht wirklich das, was für Sie gefragt, aber es gibt Ihnen eine Verkapselung von Zugang:

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

+1 für die Tatsache, dass diese Lösung nur ein zusätzliches Objekt erstellt, auch wenn ich mehrere solche "Zuweisen-einmal" Variablen haben. Die Neuzuweisung von _init.mgr ist zwar weiterhin möglich, sieht aber im Client-Code eindeutig "falsch" aus. –

+0

Die Neuzuweisung ist im Client-Code nicht möglich, da '_init' nur in' AppProperties' sichtbar ist und 'def mgr' unveränderbar ist. – Debilski

+0

Richtig, aber dafür würde eine einfache 'private var' genügen. Ich war daran interessiert, dies bei der weiteren Implementierung von 'AppProperties' zu verhindern. Ich denke, ich hätte nicht "client code" schreiben sollen. –

0

Es ist nicht genau das Gleiche, aber in vielen Fällen ist die Lösung für dieses ‚stellen Sie die Variable einmal und dann weiter verwenden 'ist einfache Unterklassen mit oder ohne eine spezielle Fabrikmethode.

0

Sie können diesen Wert immer zu einem anderen Objekt verschieben, nur einmal initialisieren und bei Bedarf darauf zugreifen.

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

bei JPP’s post Suche ich eine andere Variante gemacht haben:

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 

Diesmal sind wir perfekt erlaubt die var mehrfach zuordnen, aber wir müssen den richtigen Anmeldeinformationen in ihrem Umfang haben. Der Berechtigungsnachweis wird erstellt und bei der ersten Zuweisung zurückgegeben, weshalb wir ihn sofort unter implicit val credential = mgr := new FileManager(config) speichern müssen. Wenn die Berechtigung falsch ist, funktioniert sie nicht.

(Beachten Sie, dass die impliziten Anmeldeinformationen funktionieren nicht, wenn es mehr Anmeldeinformationen im Rahmen ist, weil sie die gleiche Art haben werden. Es könnte möglich sein, dies zu umgehen, aber ich bin im Moment nicht sicher.)

+1

Wäre es nicht möglich, die' SetOnceCredential' Klasse vom 'SetOnce' Objekt in die' SetOnce' Klasse zu verschieben, um die Mehrfach-Anmeldeinformationen zu vermeiden Problem, das du erwähnst? Es scheint so zu sein, dass wir die zwischengespeicherten Anmeldeinformationen sogar in 'Schlüssel' loswerden könnten. Ich mag die Idee, aber jetzt ist die Kompilierzeit-Garantie geschwächt, da jede Zuordnung kompiliert (mit dem Standard-Null-Wert), aber zur Laufzeit fehlschlagen würde. –

+0

Sie haben Recht. Verschieben Sie es innerhalb der Klasse behebt dies natürlich. – Debilski

+0

Sehr schön! Die letzte Sache, die ich nicht mag, ist, dass Ihre letzte Zeile, 'mgr1: = new FileManager (new Config)', obwohl sie zur Laufzeit fehlschlägt, kompiliert und nicht anzeigt, dass sie tatsächlich einen Berechtigungsnachweis benötigt. –

Verwandte Themen