Ошибка постоянного назначения в Ruby?

Мы поймали какой-то код на Ruby, который кажется странным, и мне было интересно, может ли кто-нибудь объяснить его:

$ irb
irb(main):001:0> APPLE = 'aaa'
=> "aaa"
irb(main):002:0> banana = APPLE
=> "aaa"
irb(main):003:0> banana << 'bbb'
=> "aaabbb"
irb(main):004:0> banana
=> "aaabbb"
irb(main):005:0> APPLE
=> "aaabbb"

Уловить это? Константа была добавлена ​​одновременно с локальной переменной.

Известное поведение? Ожидал?


person aronchick    schedule 09.06.2010    source источник


Ответы (4)


Уловить это? Константа была добавлена ​​одновременно с локальной переменной.

Нет, он не был добавлен, как и локальная переменная.

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

Единственные две вещи, которые вы можете делать с переменными или константами, — это разыменовывать их и присваивать им значения.

Константа здесь является отвлекающим маневром, она совершенно не имеет отношения к приведенному примеру. Единственное, что имеет значение, это то, что во всем примере есть только один объект. Этот единственный объект доступен под двумя разными именами. Если объект меняется, то объект меняется. Период. Он не разделяется таинственным образом на две части. Какое имя вы используете для просмотра измененного объекта, не имеет значения. В любом случае есть только один объект.

Это работает точно так же, как и в любом другом языке программирования: если у вас есть несколько ссылок на изменяемый объект, скажем, в Python, Java, C#, C++, C, Lisp, Smalltalk, JavaScript, PHP, Perl или где-то еще, то любое изменение к этому объекту будет виден независимо от того, какая ссылка используется, даже если некоторые из этих ссылок являются final или const или как-то еще, что этот конкретный язык называет это.

Просто так работает разделяемое изменяемое состояние, и это лишь одна из многих причин, почему разделяемое изменяемое состояние — это плохо.

В Ruby вы обычно можете вызвать метод freeze для любого объекта, чтобы сделать его неизменяемым. Однако, опять же, здесь вы изменяете объект, так что любой, у кого есть ссылка на этот объект, внезапно обнаружит, что объект стал неизменяемым. Поэтому на всякий случай вам нужно сначала скопировать объект, вызвав dup. Но, конечно, этого тоже недостаточно, если вы думаете о массиве, например: если вы dup массив, вы получите другой массив, но объекты внутри array все те же, что и в исходном массиве. И если вы freeze массив, то вы больше не можете изменять массив, но объекты в массиве вполне могут быть изменены:

ORIG = ['Hello']
CLONE = ORIG.dup.freeze
CLONE[0] << ', World!'
CLONE # => ['Hello, World!']

Это общее изменяемое состояние для вас. Единственный способ избежать этого безумия — либо отказаться от разделяемого состояния (например, акторное программирование: если его никто не видит, то не имеет значения, как часто и когда оно меняется), либо от изменяемого состояния (т. е. функционального программирования: если оно никогда не изменяется). меняется, то не имеет значения, сколько других это увидит).

Тот факт, что одна из двух переменных в исходном примере на самом деле является константой, совершенно не имеет отношения к проблеме. Между переменной и константой в Ruby есть два основных различия: у них разные правила поиска, и константы генерируют предупреждение, если им присваивается значение более одного раза. Но в этом примере правила поиска не имеют значения, а константа присваивается только один раз, поэтому в этом случае действительно нет разницы между переменной и константой.

person Jörg W Mittag    schedule 10.06.2010
comment
Можете ли вы уважать переменные/константы в рубине или можете присвоить им только ноль? - person Andrew Grimm; 10.06.2010
comment
Кроме того, если объект создан и ничему не назначен, существует ли он до тех пор, пока не будет собран мусор? - person Andrew Grimm; 10.06.2010

Известное поведение. Константы не означают, что вы не можете изменить объект, на который они ссылаются, просто они выдадут вам предупреждение (и только предупреждение), если вы присвоите их другому объекту.

Короче говоря, рубиновые константы не таковы.

Примечание. Это поведение указано в ответ на вопрос "О каких Ruby Gotchas следует предупредить новичка?" Это стоит прочитать.

person Andrew Grimm    schedule 09.06.2010
comment
В основном это постоянные ссылки. Java работает в основном так же. - person Benjamin Oakes; 10.06.2010
comment
@Бенджамин Оукс: Да, именно так. И не только Java, а вообще любой язык программирования на планете. Хотя в Ruby они даже не такие постоянные: их переназначение вызовет только предупреждение, а не ошибку. - person Jörg W Mittag; 11.06.2010
comment
@Jörg W Mittag: Совершенно верно. Если кто-нибудь знает способ вызвать исключение при переназначении констант, мне было бы очень интересно. Однако возможность замораживания объектов весьма полезна. - person Benjamin Oakes; 11.06.2010
comment
Я задал вопрос из своего последнего комментария: повторное назначение константы в ruby"> stackoverflow.com/questions/3023617/ - person Benjamin Oakes; 11.06.2010

Вы можете заморозить константы, если хотите, чтобы они были неизменяемыми:

>> APPLE = 'aaa'
=> "aaa"
>> banana = APPLE
=> "aaa"
>> APPLE.freeze
=> "aaa"
>> banana.frozen?
=> true
>> banana << 'bbb'
TypeError: can't modify frozen string
    from (irb):5:in `<<'
    from (irb):5
person zaius    schedule 09.06.2010
comment
Нет, в Ruby нельзя заморозить константы, только объекты. - person Jörg W Mittag; 10.06.2010
comment
@Benjamin: Я думаю, что Йорг говорит о том, что вы замораживаете только объект, на который ссылается константа, а не саму константу. - person Andrew Grimm; 10.06.2010
comment
@Andrew: Ах, я неправильно понял, что написал Йорг. То, что вы сказали, правильно. - person Benjamin Oakes; 10.06.2010

Константы в Ruby не являются "константами". С таким же успехом вы можете использовать любое другое имя; помещение их во все заглавные буквы ничего не меняет с точки зрения интерпретатора в объекте, если только вы не попытаетесь изменить адрес указателя.

Если вы посмотрите на это таким образом, поведение очевидно и необходимо; Apple — это указатель на строковый объект, как и банан. Затем вы редактируете объект, на который указывает банан. Apple указывает на тот же самый объект, поэтому изменение отражается и там.

person Justin L.    schedule 10.06.2010
comment
Правила доступа к константам отличаются от правил доступа к переменным. - person Andrew Grimm; 10.06.2010