Tugas konstan dinamis

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

memberi saya kesalahan:

SyntaxError: kesalahan penetapan konstan dinamis

Mengapa ini dianggap sebagai konstanta dinamis? Saya hanya menetapkan string untuk itu.

kaca
sumber
34
Constant Constant adalah sesuatu seperti Air Kering? :)
fl00r
39
Itu tidak mengatakan bahwa konstanta itu dinamis. Dikatakan bahwa tugasnya dinamis.
sepp2k

Jawaban:

141

Masalah Anda adalah bahwa setiap kali Anda menjalankan metode, Anda menetapkan nilai baru ke konstanta. Ini tidak diperbolehkan, karena membuat konstan tidak konstan; meskipun isi string sama (untuk saat ini, bagaimanapun), objek string yang sebenarnya itu sendiri berbeda setiap kali metode ini dipanggil. Sebagai contoh:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Mungkin jika Anda menjelaskan kasus penggunaan Anda - mengapa Anda ingin mengubah nilai konstanta dalam suatu metode - kami dapat membantu Anda dengan implementasi yang lebih baik.

Mungkin Anda lebih suka memiliki variabel instan di kelas?

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"

Jika Anda benar - benar ingin mengubah nilai konstanta dalam sebuah metode, dan konstanta Anda adalah sebuah String atau Array, Anda dapat 'menipu' dan menggunakan #replacemetode ini untuk menyebabkan objek mengambil nilai baru tanpa benar-benar mengubah objek:

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
sumber
19
OP tidak pernah mengatakan dia ingin mengubah nilai konstanta tetapi hanya ingin memberikan nilai. Kasus penggunaan yang sering menyebabkan kesalahan Ruby ini adalah ketika Anda membangun nilai dalam metode dari aset run-time lainnya (variabel, argumen baris perintah, ENV), biasanya dalam konstruktor misalnya def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. Itu salah satu kasus di mana Ruby tidak memiliki cara sederhana.
Arnaud Meuret
2
@ArnaudMeuret Untuk kasus ini Anda menginginkan variabel instan (misal @variable), bukan konstanta. Kalau tidak, Anda akan menugaskan kembali DBsetiap kali Anda membuat instance baru dari kelas itu.
Ajedi32
2
@ Ajedi32 Situasi ini biasanya muncul dari kendala eksternal bukan pilihan desain seperti contoh saya dengan Sequel. Maksud saya adalah bahwa menetapkan nilai ke konstanta diizinkan oleh Ruby dalam lingkup tertentu dan bukan yang lain. Dulu tergantung pada pengembang untuk memilih dengan bijak kapan melakukan tugas. Ruby berubah pada ini. Tidak untuk kebaikan semua orang.
Arnaud Meuret
2
@ArnaudMeuret Saya akui saya belum pernah menggunakan Sequel sebelumnya jadi saya tidak bisa mengatakan ini dengan kepastian 100%, tetapi hanya melihat sekilas dokumentasi untuk Sequel. Saya tidak melihat apa pun yang mengatakan bahwa Anda HARUS menetapkan hasil Sequel.connectuntuk konstanta bernama DB . Bahkan, dokumentasi secara eksplisit mengatakan bahwa itu hanya rekomendasi. Itu tidak terdengar seperti kendala eksternal bagi saya.
Ajedi32
@ Ajedi32 1) Saya tidak pernah menulis itu (nama konstanta atau bahkan Anda harus menyimpannya di suatu tempat) itu hanya contoh 2) Kendala adalah bahwa perangkat lunak Anda mungkin tidak memiliki informasi yang diperlukan sampai Anda biasanya berada dalam konteks dinamis .
Arnaud Meuret
69

Karena konstanta di Ruby tidak dimaksudkan untuk diubah, Ruby tidak menyarankan Anda untuk menugaskan mereka dalam bagian kode yang mungkin dieksekusi lebih dari sekali, seperti metode di dalam.

Dalam keadaan normal, Anda harus mendefinisikan konstanta di dalam kelas itu sendiri:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Jika karena alasan tertentu Anda benar-benar perlu mendefinisikan konstanta di dalam suatu metode (mungkin untuk beberapa jenis pemrograman), Anda dapat menggunakan 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"

Lagi-lagi, const_setbukan sesuatu yang harus benar-benar Anda gunakan dalam keadaan normal. Jika Anda tidak yakin apakah Anda benar - benar ingin ditugaskan ke konstanta dengan cara ini, Anda mungkin ingin mempertimbangkan salah satu alternatif berikut:

Variabel kelas

Variabel kelas berperilaku seperti konstanta dalam banyak hal. Mereka adalah properti di kelas, dan mereka dapat diakses di subclass dari kelas yang mereka tetapkan.

Perbedaannya adalah bahwa variabel kelas dimaksudkan untuk dapat dimodifikasi, dan karenanya dapat ditugaskan ke dalam metode tanpa masalah.

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"

Atribut kelas

Atribut kelas adalah semacam "variabel instan pada kelas". Mereka berperilaku sedikit seperti variabel kelas, kecuali bahwa nilainya tidak dibagi dengan subclass.

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"

Variabel instan

Dan hanya untuk kelengkapan yang mungkin harus saya sebutkan: jika Anda perlu menetapkan nilai yang hanya dapat ditentukan setelah kelas Anda dipakai, ada kemungkinan Anda benar-benar mencari variabel instan yang lama.

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
sumber
33

Di Ruby, variabel apa pun yang namanya dimulai dengan huruf kapital adalah konstan dan Anda hanya dapat menetapkan satu kali. Pilih salah satu dari alternatif ini:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
sumber
2
Syukurlah seseorang mengatakan bahwa "variabel apa pun yang namanya dimulai dengan huruf kapital adalah konstan!"
ubienewbie
0

Anda tidak dapat memberi nama variabel dengan huruf kapital atau Ruby akan menganggapnya sebagai konstanta dan ingin agar variabel itu tetap konstan, dalam hal ini mengubah nilainya akan menjadi kesalahan yang merupakan "kesalahan penetapan konstan yang dinamis". Dengan huruf kecil harus baik-baik saja

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
Jose Paez
sumber
0

Ruby tidak suka bahwa Anda menetapkan konstanta di dalam metode karena berisiko penugasan kembali. Beberapa jawaban SO sebelum saya memberikan alternatif untuk menetapkannya di luar metode - tetapi di kelas, yang merupakan tempat yang lebih baik untuk menetapkannya.

John
sumber
1
Weicome untuk SO John. Anda dapat mempertimbangkan untuk meningkatkan jawaban ini dengan menambahkan beberapa kode contoh dari apa yang Anda gambarkan.
Cleptus
0

Terima kasih banyak kepada Dorian dan Phrogz untuk mengingatkan saya tentang metode array (dan hash) #replace, yang dapat "mengganti konten array atau hash."

Gagasan bahwa nilai KONSTAN dapat diubah, tetapi dengan peringatan yang menjengkelkan, adalah salah satu dari beberapa langkah keliru konseptual Ruby - ini harus sepenuhnya tidak berubah, atau membuang ide konstan sama sekali. Dari sudut pandang seorang pembuat kode, sebuah konstanta bersifat deklaratif dan disengaja, sebuah sinyal bagi yang lain bahwa "nilai ini benar-benar tidak dapat diubah begitu dideklarasikan / ditugaskan."

Tetapi kadang-kadang "deklarasi yang jelas" sebenarnya menyita kesempatan lain yang bermanfaat di masa depan. Sebagai contoh...

Ada yang kasus penggunaan yang sah di mana nilai "konstan ini" mungkin benar-benar perlu diubah: misalnya, re-loading ARGV dari prompt loop repl-seperti, kemudian Siarang ARGV melalui lebih (berikutnya) OptionParser.parse! panggilan - voila! Memberikan "command line args" sebuah utilitas dinamis yang sama sekali baru.

Masalah praktis adalah baik dengan asumsi dugaan bahwa "ARGV harus konstan", atau dalam metode initialize optparse sendiri, yang keras-kode penugasan ARGV ke @default_argv misalnya var untuk diproses selanjutnya - bahwa array (ARGV) benar-benar harus menjadi parameter, mendorong penguraian ulang dan penggunaan kembali, jika sesuai. Parameterisasi yang tepat, dengan standar yang sesuai (katakanlah, ARGV) akan menghindari keharusan untuk mengubah ARGV "konstan". Hanya sekitar 2 ¢ - layak dipikirkan ...

Lorin Ricker
sumber