Je passe par Ruby Koans, et j'ai frappé le # 41 qui je crois est le suivant:
def test_default_value_is_the_same_object
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal ["uno","dos"], hash[:one]
assert_equal ["uno","dos"], hash[:two]
assert_equal ["uno","dos"], hash[:three]
assert_equal true, hash[:one].object_id == hash[:two].object_id
end
Il ne pouvait pas comprendre le comportement, je l'ai donc recherché sur Google et j'ai trouvé comportement étrange Ruby lors de l'utilisation de la valeur par défaut de Hash, par exemple Hash.new ([]) qui répondait bien à la question .
Je comprends donc comment cela fonctionne, ma question est, pourquoi une valeur par défaut telle qu'un entier qui est incrémenté n'est-elle pas modifiée pendant l'utilisation? Par exemple:
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |Word| frequencies[Word] += 1 }
Cela prendra l'entrée de l'utilisateur et comptera le nombre de fois où chaque mot est utilisé, cela fonctionne car la valeur par défaut de 0 est toujours utilisée.
J'ai le sentiment que cela a à voir avec le <<
opérateur mais j'aimerais une explication.
Les autres réponses semblent indiquer que la différence de comportement est due au fait que Integer
s est immuable et Array
s est mutable. Mais c'est trompeur. La différence n'est pas que le créateur de Ruby a décidé de rendre l'un immuable et l'autre mutable. La différence est que vous, le - programmeur a décidé de muter l'un mais pas l'autre.
La question n'est pas de savoir si Array
s sont mutables, la question est de savoir si vous mute it.
Vous pouvez obtenir les deux comportements que vous voyez ci-dessus, simplement en utilisant Array
s. Observer:
Array
par défaut avec mutationhsh = Hash.new([])
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
Array
par défaut sans mutationhsh = Hash.new([])
hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']
hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array
hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.
Array
différent à chaque fois avec mutationhsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
C'est parce que Array
in Ruby est un objet modifiable, vous pouvez donc le changer d'état interne, mais Fixnum
n'est pas modifiable. Ainsi, lorsque vous incrémentez la valeur à l'aide de +=
en interne, il obtient cela (supposons que i
est notre référence à Fixnum
objet):
i
raw_tmp
)raw_tmp + 1
i
Comme vous pouvez le voir, nous avons créé un nouvel objet et i
fait maintenant référence à quelque chose de différent qu'au début.
En revanche, lorsque nous utilisons Array#<<
ça marche comme ça:
arr
Donc, comme vous pouvez le voir, c'est beaucoup plus simple, mais cela peut provoquer des bugs. L'un d'eux que vous avez dans votre question, un autre est la course aux threads lorsque le stand essaie d'ajouter simultanément 2 éléments ou plus. Parfois, vous pouvez terminer avec seulement certains d'entre eux et avec des thrashes en mémoire, lorsque vous utilisez +=
sur les tableaux également, vous vous débarrasserez de ces deux problèmes (ou du moins minimiserez l'impact).
Dans doc , la définition d'une valeur par défaut a le comportement suivant:
Renvoie la valeur par défaut, la valeur qui serait retournée par hsh si la clé n'existait pas dans hsh. Voir aussi Hash :: new et Hash # default =.
Par conséquent, chaque fois que frequencies[Word]
n'est pas défini, la valeur de cette clé individuelle est définie sur 0.
La raison de la différence entre les deux blocs de code est que les tableaux sont mutables dans Ruby, tandis que les entiers ne le sont pas.