Por que usar símbolos como chaves hash em Ruby?

Muitas vezes as pessoas usam símbolos como chaves em um hash Ruby.

Qual é a vantagem sobre o uso de uma string?

Por exemplo:

hash[:name] 

vs.

 hash['name'] 

TL; DR:

A utilização de símbolos não apenas economiza tempo ao fazer comparações, mas também economiza memory, porque eles são armazenados apenas uma vez.

Os Símbolos Ruby são imutáveis ​​(não podem ser alterados), o que torna a procura de algo muito mais fácil

Resposta curta (ish):

A utilização de símbolos não apenas economiza tempo ao fazer comparações, mas também economiza memory, porque eles são armazenados apenas uma vez.

Símbolos em Ruby são basicamente “strings imutáveis” .. isso significa que eles não podem ser alterados, e implica que o mesmo símbolo quando referenciado várias vezes em todo o seu código-fonte, é sempre armazenado como a mesma entidade, eg tem o mesmo id de object .

Strings por outro lado são mutáveis , elas podem ser alteradas a qualquer momento. Isto implica que Ruby precisa armazenar cada string que você menciona em seu código-fonte em uma entidade separada, por exemplo, se você tiver uma string “name” várias vezes mencionada em seu código-fonte, Ruby precisa armazenar todos em objects String separados, porque eles pode mudar mais tarde (essa é a natureza de uma string Ruby).

Se você usar uma string como uma chave Hash, Ruby precisa avaliar a string e examinar seu conteúdo (e calcular uma function hash) e comparar o resultado com os valores (hashed) das chaves que já estão armazenadas no Hash. .

Se você usar um símbolo como uma chave Hash, é implícito que é imutável, então o Ruby basicamente pode fazer uma comparação entre a function (hash da) object-id contra os ids-object (hash) das chaves que já estão armazenadas o Hash. (muito mais rapido)

Inferior: Cada símbolo consome um slot na tabela de símbolos do interpretador Ruby, que nunca é liberada. Símbolos nunca são coletados de lixo. Portanto, um caso de canto é quando você tem um grande número de símbolos (por exemplo, os gerados automaticamente). Nesse caso, você deve avaliar como isso afeta o tamanho do seu interpretador Ruby.

Notas:

Se você fizer comparações de strings, Ruby pode comparar símbolos apenas por seus ids de object, sem ter que avaliá-los. Isso é muito mais rápido do que comparar strings, que precisam ser avaliadas.

Se você acessar um hash, Ruby sempre aplicará uma function hash para calcular uma “chave hash” de qualquer chave que você usar. Você pode imaginar algo como um hash MD5. E então Ruby compara essas “chaves de hash” umas contra as outras.

Resposta longa:

http://www.reactive.io/tips/2009/01/11/the-difference-entre-ruby-symbols-and-strings

http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol

O motivo é a eficiência, com vários ganhos em uma string:

  1. Símbolos são imutáveis, então a pergunta “o que acontece se a chave mudar?” não precisa ser perguntado.
  2. As cadeias de caracteres são duplicadas no seu código e geralmente ocupam mais espaço na memory.
  3. Pesquisas hash devem calcular o hash das chaves para compará-las. Este é O(n) para Strings e constante para Símbolos.

Além disso, o Ruby 1.9 introduziu uma syntax simplificada apenas para hash com chaves de símbolos (por exemplo, h.merge(foo: 42, bar: 6) ), e o Ruby 2.0 possui argumentos de palavra – chave que funcionam apenas para chaves de símbolos.

Notas :

1) Você pode se surpreender ao saber que o Ruby trata as chaves String diferente de qualquer outro tipo. De fato:

 s = "foo" h = {} h[s] = "bar" s.upcase! h.rehash # must be called whenever a key changes! h[s] # => nil, not "bar" h.keys h.keys.first.upcase! # => TypeError: can't modify frozen string 

Apenas para chaves de string, o Ruby usará uma cópia congelada em vez do próprio object.

2) As letras “b”, “a” e “r” são armazenadas apenas uma vez para todas as ocorrências de :bar em um programa. Antes do Ruby 2.2, era uma má idéia criar constantemente novos Symbols que nunca eram reutilizados, pois eles permaneceriam na tabela global de pesquisa de símbolos para sempre. Ruby 2.2 irá coletar lixo, então não se preocupe.

3) Na verdade, calcular o hash para um símbolo não demorou em Ruby 1.8.x, já que o ID do object foi usado diretamente:

 :bar.object_id == :bar.hash # => true in Ruby 1.8.7 

No Ruby 1.9.x, isso mudou conforme os hashes mudam de uma session para outra (incluindo os de Symbols ):

 :bar.hash # => some number that will be different next time Ruby 1.9 is ran 

Qual é a vantagem sobre o uso de uma string?

  • Styling: é o caminho Ruby
  • (Muito) lookup de valor um pouco mais rápido, uma vez que o hashing de um símbolo equivale a um hash inteiro versus o hashing de uma string.

  • Desvantagem: consome um slot na tabela de símbolos do programa que nunca é liberado.

Eu ficaria muito interessado em um follow-up sobre strings congeladas introduzidas no Ruby 2.x.

Quando você lida com várias strings vindas de uma input de texto (estou pensando em params HTTP ou payload, por meio do Rack, por exemplo), é mais fácil usar strings em todos os lugares.

Quando você lida com dezenas deles, mas eles nunca mudam (se eles são o seu “vocabulário”), eu gosto de pensar que congelá-los pode fazer a diferença. Ainda não fiz nenhum benchmark, mas acho que seria próximo do desempenho dos símbolos.