Assegnazione costante dinamica


139
class MyClass
  def mymethod
    MYCONSTANT = "blah"
  end
end

mi dà l'errore:

SyntaxError: errore di assegnazione costante dinamica

Perché questa è considerata una costante dinamica? Sto solo assegnando una stringa ad esso.


34
Costante dinamica è qualcosa come l'acqua secca? :)
fl00r

39
Non dice che la costante sia dinamica. Dice che il compito è dinamico.
sepp2k,

Risposte:


141

Il problema è che ogni volta che si esegue il metodo si assegna un nuovo valore alla costante. Questo non è permesso, poiché rende la costante non costante; anche se il contenuto della stringa è lo stesso (per il momento, comunque), l' oggetto stringa reale stesso è diverso ogni volta che viene chiamato il metodo. Per esempio:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Forse se spiegassi il tuo caso d'uso, perché vuoi cambiare il valore di una costante in un metodo, potremmo aiutarti con una migliore implementazione.

Forse preferiresti avere una variabile di istanza sulla classe?

class MyClass
  class << self
    attr_accessor :my_constant
  end
  def my_method
    self.class.my_constant = "blah"
  end
end

p MyClass.my_constant #=> nil
MyClass.new.my_method

p MyClass.my_constant #=> "blah"

Se vuoi davvero cambiare il valore di una costante in un metodo e la tua costante è una stringa o una matrice, puoi "imbrogliare" e utilizzare il #replacemetodo per fare in modo che l'oggetto acquisisca un nuovo valore senza cambiare l'oggetto:

class MyClass
  BAR = "blah"

  def cheat(new_bar)
    BAR.replace new_bar
  end
end

p MyClass::BAR           #=> "blah"
MyClass.new.cheat "whee"
p MyClass::BAR           #=> "whee"

19
L'OP non ha mai detto che voleva cambiare il valore della costante, ma voleva solo assegnare un valore. Il caso di utilizzo frequente che porta a questo errore Ruby è quando si crea il valore in un metodo da altre risorse di runtime (variabili, argomenti della riga di comando, ENV), in genere in un costruttore ad es def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. È uno di quei casi in cui Ruby non ha un modo semplice.
Arnaud Meuret,

2
@ArnaudMeuret In quel caso si desidera una variabile di istanza (ad es. @variable), Non una costante. Altrimenti verresti riassegnare DBogni volta che hai istanziato una nuova istanza di quella classe.
Ajedi32

2
@ Ajedi32 Questa situazione di solito deriva da vincoli esterni e non da scelte progettuali come il mio esempio con Sequel. Il mio punto è che l'assegnazione di un valore a una costante è consentita da Ruby in determinati ambiti e non in altri. Un tempo spettava allo sviluppatore scegliere saggiamente quando eseguire l'incarico. Ruby è cambiato su questo. Non per il bene di tutti.
Arnaud Meuret,

2
@ArnaudMeuret Devo ammettere che non ho mai usato Sequel prima, quindi non posso dirlo con certezza al 100%, ma solo dando un'occhiata alla documentazione di Sequel non vedo nulla che dica che DEVI assegnare il risultato Sequel.connecta un costante chiamato DB . In effetti, la documentazione afferma esplicitamente che si tratta solo di una raccomandazione. Non mi sembra un vincolo esterno.
Ajedi32,

@ Ajedi32 1) Non ho mai scritto che (nome della costante o addirittura che dovevi tenerlo da qualche parte) è solo un esempio 2) Il vincolo è che il tuo software potrebbe non avere le informazioni necessarie finché non ti trovi in ​​un contesto dinamico .
Arnaud Meuret,

69

Poiché le costanti in Ruby non sono pensate per essere modificate, Ruby ti scoraggia dall'assegnarle a parti di codice che potrebbero essere eseguite più di una volta, come i metodi interni.

In circostanze normali, è necessario definire la costante all'interno della classe stessa:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Se per qualche motivo hai davvero bisogno di definire una costante all'interno di un metodo (forse per qualche tipo di metaprogrammazione), puoi usare const_set:

class MyClass
  def my_method
    self.class.const_set(:MY_CONSTANT, "foo")
  end
end

MyClass::MY_CONSTANT
#=> NameError: uninitialized constant MyClass::MY_CONSTANT

MyClass.new.my_method
MyClass::MY_CONSTANT #=> "foo"

Ancora una volta, però, const_setnon è qualcosa a cui dovresti davvero ricorrere in circostanze normali. Se non sei sicuro di voler veramente assegnare le costanti in questo modo, potresti prendere in considerazione una delle seguenti alternative:

Variabili di classe

Le variabili di classe si comportano come costanti in molti modi. Sono proprietà di una classe e sono accessibili nelle sottoclassi della classe in cui sono definite.

La differenza è che le variabili di classe sono modificabili e possono quindi essere assegnate a metodi interni senza problemi.

class MyClass
  def self.my_class_variable
    @@my_class_variable
  end
  def my_method
    @@my_class_variable = "foo"
  end
end
class SubClass < MyClass
end

MyClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass
SubClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass

MyClass.new.my_method
MyClass.my_class_variable #=> "foo"
SubClass.my_class_variable #=> "foo"

Attributi di classe

Gli attributi di classe sono una sorta di "variabile di istanza su una classe". Si comportano un po 'come le variabili di classe, tranne per il fatto che i loro valori non sono condivisi con le sottoclassi.

class MyClass
  class << self
    attr_accessor :my_class_attribute
  end
  def my_method
    self.class.my_class_attribute = "blah"
  end
end
class SubClass < MyClass
end

MyClass.my_class_attribute #=> nil
SubClass.my_class_attribute #=> nil

MyClass.new.my_method
MyClass.my_class_attribute #=> "blah"
SubClass.my_class_attribute #=> nil

SubClass.new.my_method
SubClass.my_class_attribute #=> "blah"

Variabili di istanza

E solo per completezza dovrei probabilmente menzionare: se devi assegnare un valore che può essere determinato solo dopo che la tua classe è stata istanziata, ci sono buone probabilità che tu stia effettivamente cercando una semplice vecchia variabile di istanza.

class MyClass
  attr_accessor :instance_variable
  def my_method
    @instance_variable = "blah"
  end
end

my_object = MyClass.new
my_object.instance_variable #=> nil
my_object.my_method
my_object.instance_variable #=> "blah"

MyClass.new.instance_variable #=> nil

33

In Ruby, qualsiasi variabile il cui nome inizia con una lettera maiuscola è una costante e puoi assegnarla solo una volta. Scegli una di queste alternative:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end

2
Per fortuna qualcuno ha detto che "qualsiasi variabile il cui nome inizia con una lettera maiuscola è una costante!"
ubienewbie


0

Non puoi nominare una variabile con lettere maiuscole o Ruby ne assumerà una costante e vorrà che mantenga il suo valore costante, nel qual caso cambiarne il valore sarebbe un errore un "errore di assegnazione costante dinamica". Con lettere minuscole dovrebbe andare bene

class MyClass
  def mymethod
    myconstant = "blah"
  end
end

0

A Ruby non piace che tu stia assegnando la costante all'interno di un metodo perché rischia di riassegnarla. Diverse risposte SO prima di me danno l'alternativa di assegnarlo al di fuori di un metodo, ma nella classe, che è un posto migliore per assegnarlo.


1
Weicome a SO John. Puoi prendere in considerazione l'idea di migliorare questa risposta aggiungendo un codice di esempio di ciò che stai descrivendo.
Cleptus,

0

Mille grazie a Dorian e Phrogz per avermi ricordato l'array (e l'hash) metodo #replace, che può "sostituire il contenuto di un array o hash".

L'idea che il valore di una COSTANTE possa essere cambiato, ma con un fastidioso avvertimento, è uno dei pochi passi sbagliati concettuali di Ruby: questi dovrebbero essere completamente immutabili o abbandonare del tutto l'idea costante. Dal punto di vista di un programmatore, una costante è dichiarativa e intenzionale, un segnale all'altro che "questo valore è veramente immutabile una volta dichiarato / assegnato".

Ma a volte una "dichiarazione ovvia" preclude in realtà altre utili opportunità future. Per esempio...

Ci sono casi di utilizzo legittimi in cui il valore di una "costante di" potrebbe davvero bisogno di essere cambiato: per esempio, ri-caricamento ARGV dal prompt-loop REPL-like, quindi eseguire nuovamente ARGV attraverso più (successiva) OptionParser.parse! chiama - voilà! Fornisce "argent a riga di comando" un'utilità dinamica completamente nuova.

Il problema pratico è o con il presupposto presunto che "ARGV deve essere una costante", o nel metodo di inizializzazione di optparse, che codifica in modo rigido l'assegnazione di ARGV all'istanza var @default_argv per l'elaborazione successiva - quell'array (ARGV) in realtà dovrebbe essere un parametro, incoraggiando il re-analisi e il riutilizzo, se del caso. Una corretta parametrizzazione, con un'adeguata impostazione predefinita (ad esempio, ARGV) eviterebbe la necessità di cambiare mai l'ARGV "costante". Solo qualche 2 ¢ di pensieri ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.