Dynamiczne przypisanie stałej

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

daje mi błąd:

SyntaxError: błąd dynamicznego przypisania stałej

Dlaczego jest to uważane za dynamiczną stałą? Po prostu przypisuję do niego ciąg.

lustro
źródło
34
Stała dynamiczna to coś w rodzaju suchej wody? :)
fl00r
39
Nie mówi, że stała jest dynamiczna. Mówi, że zadanie jest dynamiczne.
sepp2k

Odpowiedzi:

141

Twój problem polega na tym, że za każdym razem, gdy uruchamiasz metodę, przypisujesz stałą nową wartość. Jest to niedozwolone, ponieważ powoduje, że stała nie jest stała; mimo że zawartość łańcucha jest taka sama (w każdym razie w tej chwili), sam obiekt string jest inny przy każdym wywołaniu metody. Na przykład:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Być może gdybyś wyjaśnił swój przypadek użycia - dlaczego chcesz zmienić wartość stałej w metodzie - moglibyśmy pomóc Ci w lepszej implementacji.

Może wolisz mieć zmienną instancji w klasie?

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"

Jeśli naprawdę chcesz zmienić wartość stałej w metodzie, a twoją stałą jest String lub Array, możesz „oszukać” i użyć #replacemetody, aby spowodować, że obiekt przybierze nową wartość bez faktycznej zmiany obiektu:

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"
Phrogz
źródło
19
OP nigdy nie powiedział, że chce zmienić wartość stałej, ale chciał tylko przypisać wartość. Częstym przypadkiem użycia prowadzącym do tego błędu Rubiego jest tworzenie wartości w metodzie z innych zasobów czasu wykonywania (zmienne, argumenty wiersza poleceń, ENV), zwykle w konstruktorze, np def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. To jeden z tych przypadków, w których Ruby nie ma prostego sposobu.
Arnaud Meuret
2
@ArnaudMeuret W tym przypadku potrzebujesz zmiennej instancji (np. @variable), A nie stałej. W przeciwnym razie będziesz ponownie przypisywać za DBkażdym razem, gdy utworzysz nową instancję tej klasy.
Ajedi32
2
@ Ajedi32 Ta sytuacja zwykle wynika z zewnętrznych ograniczeń, a nie z wyborów projektowych, takich jak mój przykład z Sequelem. Chodzi mi o to, że przypisywanie wartości stałej jest dozwolone przez Rubiego w niektórych zakresach, a nie w innych. Kiedyś do programisty należał mądry wybór, kiedy wykonać zadanie. Ruby się w tym zmienił. Nie dla każdego jest dobre.
Arnaud Meuret
2
@ArnaudMeuret Przyznaję, że nigdy wcześniej nie korzystałem z Sequela, więc nie mogę tego powiedzieć ze 100% pewnością, ale przeglądając dokumentację Sequela, nie widzę nic, co mówi, że MUSISZ przypisać wynik Sequel.connectdo stałej o nazwie DB . W rzeczywistości dokumentacja wyraźnie mówi, że to tylko zalecenie. Dla mnie to nie brzmi jak zewnętrzne ograniczenie.
Ajedi32,
@ Ajedi32 1) Nigdy tego nie napisałem (nazwa stałej lub nawet to, że musiałeś ją gdzieś przechowywać) to tylko przykład 2) Ograniczenie polega na tym, że twoje oprogramowanie może nie mieć niezbędnych informacji, dopóki zwykle nie jesteś w dynamicznym kontekście .
Arnaud Meuret
69

Ponieważ stałe w Rubim nie powinny być zmieniane, Ruby odradza przypisywanie do nich części kodu, które mogą być wykonywane więcej niż jeden raz, takich jak metody wewnętrzne.

W normalnych okolicznościach należy zdefiniować stałą wewnątrz samej klasy:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Jeśli z jakiegoś powodu naprawdę potrzebujesz zdefiniować stałą wewnątrz metody (być może dla jakiegoś typu metaprogramowania), możesz użyć 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"

Znowu jednak const_setnie jest czymś, do czego naprawdę powinieneś uciekać się w normalnych okolicznościach. Jeśli nie masz pewności, czy naprawdę chcesz przypisywać stałe w ten sposób, możesz rozważyć jedną z następujących alternatyw:

Zmienne klasowe

Zmienne klasowe na wiele sposobów zachowują się jak stałe. Są właściwościami klasy i są dostępne w podklasach klasy, w której zostały zdefiniowane.

Różnica polega na tym, że zmienne klas mają być modyfikowalne i dlatego można je przypisać do metod wewnętrznych bez problemu.

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"

Atrybuty klas

Atrybuty klasy to rodzaj „zmiennej instancji w klasie”. Zachowują się trochę jak zmienne klasowe, z tym wyjątkiem, że ich wartości nie są współdzielone z podklasami.

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"

Zmienne instancji

I dla kompletności powinienem prawdopodobnie wspomnieć: jeśli chcesz przypisać wartość, która może być określona tylko po utworzeniu instancji twojej klasy, istnieje duża szansa, że ​​faktycznie szukasz zwykłej starej zmiennej instancji.

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
Ajedi32
źródło
33

W Rubim każda zmienna, której nazwa zaczyna się od dużej litery, jest stałą i możesz ją przypisać tylko raz. Wybierz jedną z tych alternatyw:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
źródło
2
Dzięki Bogu ktoś wspomniał, że „każda zmienna, której nazwa zaczyna się od dużej litery, jest stałą!”
ubienewbie
0

Nie możesz nazwać zmiennej wielkimi literami, inaczej Ruby założy, że jest to stała i będzie chciał, aby zachowała stałą wartość. W takim przypadku zmiana jej wartości byłaby błędem i „błędem dynamicznego przypisania stałej”. Z małymi literami powinno być dobrze

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
Jose Paez
źródło
0

Rubiemu nie podoba się to, że przypisujesz stałą wewnątrz metody, ponieważ istnieje ryzyko ponownego przypisania. Kilka odpowiedzi SO przede mną daje alternatywę przypisywania jej poza metodą - ale w klasie, co jest lepszym miejscem do jej przypisania.

Jan
źródło
1
Weicome dla SO John. Możesz rozważyć ulepszenie tej odpowiedzi, dodając przykładowy kod tego, co opisujesz.
Cleptus,
0

Wielkie podziękowania dla Doriana i Phrogza za przypomnienie mi o metodzie tablicowej (i haszującej) #replace, która może „zastąpić zawartość tablicy lub skrótu”.

Pogląd, że wartość STAŁA można zmienić, ale z irytującym ostrzeżeniem, jest jednym z niewielu błędnych kroków koncepcyjnych Rubiego - powinny one albo być w pełni niezmienne, albo całkowicie odrzucić stałą ideę. Z punktu widzenia programisty stała jest deklaratywna i zamierzona, jest sygnałem dla innych, że „ta wartość jest naprawdę niezmienna po zadeklarowaniu / przypisaniu”.

Czasami jednak „oczywista deklaracja” faktycznie wyklucza inne, przydatne w przyszłości możliwości. Na przykład...

Tam przypadki legalnego wykorzystania gdzie wartość danej „Constant za” może naprawdę trzeba być zmieniony, na przykład: ponowne ładowanie ARGV z REPL podobny szybkiej pętli, a następnie ponowne uruchomienie ARGV thru więcej (późniejszej) OptionParser.parse! rozmowy - voila! Daje „argumentom wiersza poleceń” zupełnie nowe dynamiczne narzędzie.

Praktyczny problem jest albo z przypuszczalnego założeniu, że „ARGV musi być ciągle”, lub w oddzielnym metody initialize optparse, która twardych koduje przyporządkowanie ARGV do @default_argv przykład var do dalszej obróbki - że tablica (ARGV) rzeczywiście powinien być parametrem zachęcającym do ponownej analizy i ponownego wykorzystania, w stosownych przypadkach. Właściwa parametryzacja z odpowiednią wartością domyślną (powiedzmy ARGV) pozwoliłaby uniknąć konieczności zmiany „stałej” wartości ARGV. Zaledwie 2 grosze myśli ...

Lorin Ricker
źródło