48

私はRuby Koansを調べています.41番にヒットしましたが、これは次のとおりだと思います:

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

動作を理解できなかったので、Google で検索したところ、Hash のデフォルト値を使用した場合の奇妙な Ruby の動作が見つかりました。

だから私はそれがどのように機能するかを理解しています.私の質問は、インクリメントされる整数などのデフォルト値が使用中に変更されないのはなぜですか? 例えば:

puts "Text please: "
text = gets.chomp

words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }

これはユーザー入力を受け取り、各単語が使用された回数をカウントします。デフォルト値の 0 が常に使用されるため、機能します。

<<オペレーターに関係があると思いますが、説明が欲しいです。

4

3 に答える 3

124

他の回答は、動作の違いはIntegers が不変であり、Arrays が可変であるためであることを示しているようです。しかし、それは誤解を招くものです。違いは、Ruby の作成者が一方を不変にし、もう一方を可変にすることを決定したことではありません。違いは、プログラマーが一方変更することを決定し、もう一方を変更しないことです。

問題はArrays が変更可能かどうかではなく、変更できるかどうかです

sを使用するだけで、上記の両方の動作を取得できますArray。観察:

Array突然変異を伴う1 つのデフォルト

hsh = 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突然変異のない1 つのデフォルト

hsh = 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突然変異で毎回違う、新しい

hsh = 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 => [] }
于 2013-04-23T10:17:01.073 に答える
4

ArrayRuby ではミュータブル オブジェクトなので、内部状態を変更することはできますが、ミュータブルFixnumではありません。したがって、内部的に使用して値をインクリメントすると、次のようになります (これがオブジェクトへの参照+=であると仮定します)。iFixnum

  1. によって参照されるオブジェクトを取得するi
  2. 内部値を取得します(名前を付けましょうraw_tmp
  3. 内部値が新しいオブジェクトを作成しますraw_tmp + 1
  4. 作成されたオブジェクトへの参照を割り当てますi

ご覧のとおり、新しいオブジェクトを作成iし、最初とは異なるものを参照しています。

一方、使用すると、次のArray#<<ように機能します。

  1. によって参照されるオブジェクトを取得するarr
  2. その内部状態に、指定された要素を追加します

ご覧のとおり、はるかに単純ですが、いくつかのバグが発生する可能性があります。そのうちの 1 つは質問にあり、もう 1 つは、ブースが 2 つ以上の要素を同時に追加しようとしている場合のスレッド レースです。場合によっては、それらの一部のみで終了し、メモリ内でスラッシュが発生することがあります+=。配列でも使用すると、これらの問題の両方を取り除くことができます (または少なくとも影響を最小限に抑えることができます)。

于 2013-04-23T01:35:59.060 に答える