Como entender símbolos em Ruby

85

Apesar de ler " Understanding Ruby Symbols ", ainda estou confuso com a representação dos dados na memória quando se trata de usar símbolos. Se um símbolo, dois deles contidos em objetos diferentes, existe no mesmo local de memória, então como é que eles contêm valores diferentes ? Eu esperava que o mesmo local de memória contivesse o mesmo valor.

Esta é uma citação do link:

Ao contrário das strings, os símbolos com o mesmo nome são inicializados e existem na memória apenas uma vez durante uma sessão de ruby

Não entendo como consegue diferenciar os valores contidos em um mesmo local de memória.

Considere este exemplo:

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1e patient2ambos são hashes, tudo bem. :rubyno entanto, é um símbolo. Se tivéssemos de produzir o seguinte:

patient1.each_key {|key| puts key.to_s}

Então, qual será a saída? "red", ou "programming"?

Esquecendo os hashes por um segundo, estou pensando que um símbolo é um ponteiro para um valor. As perguntas que tenho são:

  • Posso atribuir um valor a um símbolo?
  • Um símbolo é apenas um ponteiro para uma variável com um valor?
  • Se os símbolos são globais, isso significa que um símbolo sempre aponta para uma coisa?
Kezzer
fonte
1
Ele irá imprimir ": ruby", porque você está imprimindo um símbolo. Se você disser puts patient1[:ruby], ele imprimirá "vermelho", se você disser puts patient2[:ruby], ele imprimirá "programação".
ROSS
1
Um símbolo NÃO é um ponteiro para um valor. Internamente, um símbolo é apenas um número inteiro.
akuhn

Respostas:

62

Considere isto:

x = :sym
y = :sym
(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__) # => true

x = "string"
y = "string"
(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__) # => false

Portanto, no entanto, você cria um objeto de símbolo, desde que seu conteúdo seja o mesmo, ele se referirá ao mesmo objeto na memória. Isso não é um problema porque um símbolo é um objeto imutável . Strings são mutáveis.


(Em resposta ao comentário abaixo)

No artigo original, o valor não está sendo armazenado em um símbolo, mas em um hash. Considere isto:

hash1 = { "string" => "value"}
hash2 = { "string" => "value"}

Isso cria seis objetos na memória - quatro objetos string e dois objetos hash.

hash1 = { :symbol => "value"}
hash2 = { :symbol => "value"}

Isso cria apenas cinco objetos na memória - um símbolo, duas strings e dois objetos hash.

anshul
fonte
O exemplo no link, no entanto, mostra os símbolos contendo valores diferentes , mas o símbolo tem o mesmo nome e o mesmo local de memória. Quando são produzidos, eles têm valores diferentes , essa é a parte que não entendo. Certamente eles deveriam conter o mesmo valor?
Kezzer
1
Acabei de fazer uma edição para tentar explicar como ainda estou confuso. Meu cérebro não consegue computar;)
Kezzer
48
Os símbolos não contêm valores, são valores. Hashes contêm valores.
Mladen Jablanović
6
É o Hash(criado por {... => ...} em seu código) que armazena pares de chave / valor, não os Symbolpróprios s. Os Symbols (por exemplo, :symbolou :symou :ruby) são as chaves dos pares. Apenas como parte de um Hash"apontam" para alguma coisa.
James A. Rosen
1
O símbolo está sendo usado como a chave no hash, não o valor, é por isso que eles podem ser diferentes. É semelhante a dizer key1 = 'ruby' e hash1 = {key1 => 'value' ...} hash2 = { chave1 => 'valor2' ...}.
Joshua Olson
53

Eu era capaz de entender os símbolos quando pensava assim. Uma string Ruby é um objeto que possui vários métodos e propriedades. As pessoas gostam de usar strings para chaves, e quando a string é usada para uma chave, todos esses métodos extras não são usados. Então eles fizeram símbolos, que são objetos de string com todas as funcionalidades removidas, exceto o que é necessário para que seja uma boa chave.

Pense nos símbolos como strings constantes.

Segfault
fonte
3
Lendo as postagens, esta provavelmente faz mais sentido para mim. : ruby ​​é apenas armazenado em algum lugar na memória, se eu usar "ruby" em algum lugar, então "ruby" novamente em algum lugar novamente, é apenas duplicação. Portanto, usar símbolos é uma maneira de reduzir a duplicação de dados comuns. Como você diz, strings constantes. Eu acho que há algum mecanismo subjacente que encontrará esse símbolo novamente para usar?
Kezzer
1
@Kezzer Esta resposta é muito boa e parece certa para mim, mas seu comentário diz algo diferente e está errado ou enganoso, seu comentário fala de duplicação de dados com strings e que esse é o motivo dos símbolos, isso é errado ou enganoso. Sim, usando o símbolo várias vezes não usará mais espaço de memória, mas você pode ter isso para strings em muitas linguagens, por exemplo, algumas linguagens de programação se você escrever "abc" e em outro lugar "abc" o compilador vê que é a mesma string de valor e a armazena no mesmo lugar tornando-o o mesmo objeto, que é chamado de string interning e o c # faz isso.
barlop
Então, é basicamente uma versão incrivelmente leve de uma corda?
stevec de
34

O símbolo :rubynão contém "red"ou "programming". O símbolo :rubyé apenas o símbolo :ruby. São os seus hashes, patient1e patient2cada um contém esses valores, em cada caso apontado pela mesma chave.

Pense da seguinte maneira: se você for à sala de estar na manhã de Natal e vir duas caixas com uma etiqueta que diz "Kezzer". On tem meias e o outro carvão. Você não vai ficar confuso e perguntar como "Kezzer" pode conter tanto meias quanto carvão, embora tenha o mesmo nome. Porque o nome não contém os presentes (ruins). Está apenas apontando para eles. Da mesma forma, :rubynão contém os valores em seu hash, apenas aponta para eles.

Jcdyer
fonte
2
Esta resposta faz todo o sentido.
Vass
Isso soa como uma mistura total de hashes e símbolos. Um símbolo não aponta para um valor, se você quiser dizer que sim quando está em um hash, bem, isso pode ser discutível, mas um símbolo não precisa estar em um hash. Você pode dizer que mystring = :steveT o símbolo não aponta para nada. Uma chave em um hash tem um valor associado e a chave pode ser um símbolo. Mas um símbolo não precisa estar em um hash.
barlop
27

Você pode estar presumindo que a declaração que você fez define o valor de um símbolo para ser algo diferente do que é. Na verdade, um símbolo é apenas um valor String "internalizado" que permanece constante. É porque eles são armazenados usando um identificador de número inteiro simples que eles são frequentemente usados, pois é mais eficiente do que gerenciar um grande número de strings de comprimento variável.

Veja o caso do seu exemplo:

patient1 = { :ruby => "red" }

Isso deve ser lido como: "declare uma variável paciente1 e defina-a como um Hash, e neste armazenamento o valor 'vermelho' sob a chave (símbolo 'rubi')"

Outra maneira de escrever isso é:

patient1 = Hash.new
patient1[:ruby] = 'red'

puts patient1[:ruby]
# 'red'

Como você está fazendo uma atribuição, não é surpreendente que o resultado que você recebe seja idêntico ao que você atribuiu em primeiro lugar.

O conceito de símbolo pode ser um pouco confuso, pois não é uma característica da maioria das outras linguagens.

Cada objeto String é distinto, mesmo se os valores forem idênticos:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148099960
# "foo" 2148099940
# "foo" 2148099920
# "bar" 2148099900
# "bar" 2148099880
# "bar" 2148099860

Cada símbolo com o mesmo valor refere-se ao mesmo objeto:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

A conversão de strings em símbolos mapeia valores idênticos para o mesmo símbolo único:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  v = v.to_sym
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

Da mesma forma, a conversão de Símbolo em String cria uma string distinta sempre:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  v = v.to_s
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148097820
# "foo" 2148097700
# "foo" 2148097580
# "bar" 2148097460
# "bar" 2148097340
# "bar" 2148097220

Você pode pensar nos valores de símbolo como sendo extraídos de uma tabela de Hash interna e pode ver todos os valores que foram codificados para símbolos usando uma chamada de método simples:

Symbol.all_values

# => [:RUBY_PATCHLEVEL, :vi_editing_mode, :Separator, :TkLSHFT, :one?, :setuid?, :auto_indent_mode, :setregid, :back, :Fail, :RET, :member?, :TkOp, :AP_NAME, :readbyte, :suspend_context, :oct, :store, :WNOHANG, :@seek, :autoload, :rest, :IN_INPUT, :close_read, :type, :filename_quote_characters=, ...

Conforme você define novos símbolos pela notação de dois pontos ou usando .to_sym, esta tabela aumentará.

Tadman
fonte
18

Os símbolos não são ponteiros. Eles não contêm valores. Os símbolos simplesmente são . :rubyé o símbolo :rubye isso é tudo. Não contém um valor, não faz nada, só existe como símbolo :ruby. O símbolo :rubyé um valor igual ao número 1. Não aponta para outro valor mais do que o número 1 faz.

Mandril
fonte
13
patient1.each_key {|key| puts key.to_s}

Então, qual será a saída? "vermelho" ou "programação"?

Nem, a saída será "ruby".

Você está confundindo símbolos e hashes. Eles não são relacionados, mas são úteis juntos. O símbolo em questão é :ruby; não tem nada a ver com os valores no hash, e sua representação interna inteira será sempre a mesma, e seu "valor" (quando convertido em uma string) sempre será "ruby".

meagar
fonte
11

Em resumo

Os símbolos resolvem o problema de criar representações imutáveis ​​legíveis por humanos que também têm o benefício de serem mais simples para o tempo de execução de pesquisar do que as strings. Pense nisso como um nome ou rótulo que pode ser reutilizado.

Por que: vermelho é melhor do que "vermelho"

Em linguagens orientadas a objetos dinâmicas, você cria estruturas de dados complexas e aninhadas com referências legíveis. O hash é um caso de uso comum que você mapeia valores para chaves exclusivas - exclusivas, pelo menos, para cada instância. Você não pode ter mais de uma chave "vermelha" por hash.

No entanto, seria mais eficiente do processador usar um índice numérico em vez de chaves de string. Assim, os símbolos foram introduzidos como um meio-termo entre velocidade e legibilidade. Os símbolos resolvem muito mais fácil do que a string equivalente. Por serem legíveis por humanos e fáceis para o tempo de execução, os símbolos são um complemento ideal para uma linguagem dinâmica.

Benefícios

Como os símbolos são imutáveis, eles podem ser compartilhados durante o tempo de execução. Se duas instâncias de hash têm uma necessidade lexicográfica ou semântica comum de um item vermelho, o símbolo: vermelho usaria aproximadamente metade da memória que a string "vermelha" teria exigido para dois hashes.

Uma vez que: red sempre resolve de volta para o mesmo local na memória, ele pode ser reutilizado em uma centena de instâncias de hash com quase nenhum aumento na memória, enquanto que usar "red" adicionará um custo de memória, já que cada instância de hash precisaria armazenar a string mutável em criação.

Não tenho certeza de como Ruby realmente implementa símbolos / string, mas claramente um símbolo oferece menos sobrecarga de implementação no tempo de execução, pois é uma representação fixa. Os símbolos de adição levam um caractere a menos para digitar do que uma string entre aspas e menos digitação é a eterna busca dos verdadeiros Rubistas.

Resumo

Com um símbolo como: vermelho, você obtém a legibilidade da representação de string com menos sobrecarga devido ao custo das operações de comparação de string e à necessidade de armazenar cada instância de string na memória.

Mark Fox
fonte
4

Eu recomendaria ler o artigo da Wikipedia sobre tabelas hash - acho que vai ajudá-lo a ter uma noção do que {:ruby => "red"}realmente significa.

Outro exercício que pode ajudá-lo a entender a situação: considere {1 => "red"}. Semanticamente, isso não significa "definir o valor de 1para "red"", o que é impossível em Ruby. Em vez disso, significa "criar um objeto Hash e armazenar o valor "red"da chave 1.

Greg Campbell
fonte
3
patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1e patient2ambos são hashes, tudo bem. :rubyno entanto, é um símbolo. Se tivéssemos de produzir o seguinte:

patient1.each_key {|key| puts key.to_s}

Então, qual será a saída? "vermelho" ou "programação"?

Nenhum, é claro. A saída será ruby. Que, BTW, você poderia ter descoberto em menos tempo do que demorou para digitar a pergunta, simplesmente digitando no IRB.

Por que teria que ser redou programming? Os símbolos sempre avaliam a si mesmos. O valor do símbolo :rubyé o :rubypróprio símbolo e a representação da string do símbolo :rubyé o valor da string "ruby".

[BTW: putssempre converte seus argumentos em strings, de qualquer maneira. Não há necessidade de ligar to_s.]

Jörg W Mittag
fonte
Não tenho o IRB na máquina atual, nem seria capaz de instalá-lo, portanto, por isso, minhas desculpas por isso.
Kezzer
2
@Kezzer: Não se preocupe, eu estava apenas curioso. Às vezes, você se enterra tão profundamente em um problema que não consegue mais ver as coisas mais simples. Quando basicamente recortei e colei sua pergunta no IRB, fiquei pensando: "por que ele mesmo não fez isso?" E não se preocupe, você não é o primeiro (nem será o último) a perguntar "o que isso imprime" quando a resposta é "basta executá-lo!" BTW: aqui está o seu IRB instantâneo, em qualquer lugar, a qualquer hora, nenhuma instalação necessária: TryRuby.Org Or Ruby-Versions.Net dá a você acesso SSH a todas as versões de MRI já lançadas + YARV + JRuby + Rubinius + REE.
Jörg W Mittag
Obrigado, estou apenas brincando com isso agora. Eu ainda estou um pouco confuso, então repassei novamente.
Kezzer
1

Sou novo em Ruby, mas acho (espero?) Que seja uma maneira simples de ver ...

Um símbolo não é uma variável ou constante. Não representa ou aponta para um valor. Um símbolo é um valor.

Tudo o que existe é uma string sem a sobrecarga do objeto. O texto e apenas o texto.

Então, é isso:

"hellobuddy"

É o mesmo que este:

:hellobuddy

Exceto que você não pode fazer, por exemplo,: hellobuddy.upcase. É o valor da string e SOMENTE o valor da string.

Da mesma forma, este:

greeting =>"hellobuddy"

É o mesmo que este:

greeting => :hellobuddy

Mas, novamente, sem a sobrecarga do objeto string.

Dave Munger
fonte
-1

Uma maneira fácil de entender isso é pensar: "e se eu estivesse usando uma corda em vez de um símbolo?

patient1 = { "ruby" => "red" }
patient2 = { "ruby" => "programming" }

Não é nada confuso, certo? Você está usando "ruby" como chave em um hash .

"ruby"é uma string literal, então esse é o valor. O endereço de memória, ou ponteiro, não está disponível para você. Cada vez que você invoca "ruby", você está criando uma nova instância dele, ou seja, criando uma nova célula de memória contendo o mesmo valor - "ruby".

O hash vai então "qual é o meu valor-chave? Ah, é "ruby". Em seguida, mapeia esse valor para" vermelho "ou" programação ". Em outras palavras, :rubynão desreferencia para "red"ou "programming". O hash mapeia :ruby para "red"ou "programming".

Compare isso se usarmos símbolos

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

O valor de :rubytambém é"ruby" , efetivamente.

Por quê? Porque os símbolos são essencialmente constantes de string . Constantes não têm várias instâncias. É o mesmo endereço de memória. E um endereço de memória tem um certo valor, uma vez desreferenciado. Para símbolos, o nome do ponteiro é o símbolo e o valor não referenciado é uma string, que corresponde ao nome do símbolo, neste caso "ruby",.

Quando em um hash, você não está usando o símbolo, o ponteiro, mas o valor diferido. Voce nao esta usando :ruby, mas"ruby" . O hash então procura a chave "ruby", o valor é "red"ou "programming", dependendo de como você definiu o hash.

A mudança de paradigma e o conceito básico é que o valor de um símbolo é um conceito completamente separado de um valor mapeado por um hash, dada uma chave desse hash.

Ahnbizcad
fonte
1
qual é a falácia ou erro nesta explicação, downvoters? curioso por aprender.
ahnbizcad de
só porque uma analogia pode ser desagradável para alguns, não significa que seja falha.
ahnbizcad
3
Não vejo nenhum erro, necessariamente, mas é extremamente difícil definir o que você está tentando dizer nesta resposta.
Engenharia reversa em
x é interpretado e conceitualizado de maneira diferente com base no contexto / entidade que faz a interpretação. bem simples.
ahnbizcad
1
reformulou a resposta. Obrigado pelo feedback.
ahnbizcad