Hiroshi, can you tell us why it's expected behavior? It looks quite surprising.
I would agree that this is surprising behaviour. It would appear that in this case, the append operator is not re-assigning the value, the way it does any other time it is used. And it would appear to be not doing this specifically in the case where the default value for the Hash is specified as an empty array. I would like to understand why this behaviour is the way it is.
The letters[:a] part of the second line returns the "default value" of the Hash because the :a key does not exist in the Hash. The << 1 part pushes a 1 onto the end of the default value, but it does not assign the default value (and certainly not a modified copy of the default value) to the :a key of the Hash.
To get the behavior you expect/desire you can use letters[:a] <<= 1, which is equivalent to letters[:a] = letters[:a] << 1, but be careful when using Hashes with default objects:
Given all the trickiness and because sometimes you don't have control over the creation of the Hash, I generally end up doing this instead:
>> # h is any Hash, e.g. from method call
>> h[:a] ||= []
=> []
>> h[:a] << 1
=> [1]
# etc...
This uses the "||=" idiom, which can obliterate an existing nil or false value, so you still have to be a little careful (though it's very rarely an issue).