Cómo entender los símbolos en Ruby

85

A pesar de leer " Comprensión de los símbolos de Ruby ", todavía estoy confundido por la representación de los datos en la memoria cuando se trata de usar símbolos. Si un símbolo, dos de ellos contenidos en objetos diferentes, existen en la misma ubicación de memoria, ¿cómo es que contienen valores diferentes ? Habría esperado que la misma ubicación de memoria contenga el mismo valor.

Esta es una cita del enlace:

A diferencia de las cadenas, los símbolos del mismo nombre se inicializan y existen en la memoria solo una vez durante una sesión de ruby

No entiendo cómo se las arregla para diferenciar los valores contenidos en la misma ubicación de memoria.

Considere este ejemplo:

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

patient1y patient2ambos son hashes, está bien. :rubysin embargo es un símbolo. Si tuviéramos que generar lo siguiente:

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

Entonces, ¿qué saldrá? "red", o "programming"?

Olvidando los hash por un segundo, creo que un símbolo es un puntero a un valor. Las preguntas que tengo son:

  • ¿Puedo asignar un valor a un símbolo?
  • ¿Es un símbolo solo un puntero a una variable con un valor?
  • Si los símbolos son globales, ¿significa eso que un símbolo siempre apunta a una cosa?
Kezzer
fuente
1
Saldrá ": ruby", porque está imprimiendo un símbolo. Si dice puts patient1[:ruby], imprimirá "rojo", si dice puts patient2[:ruby], imprimirá "programación".
ROSS
1
Un símbolo NO es un puntero a un valor. Internamente, un símbolo es solo un número entero.
akuhn

Respuestas:

62

Considera esto:

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

Por lo tanto, independientemente de cómo cree un objeto de símbolo, siempre que su contenido sea el mismo, se referirá al mismo objeto en la memoria. Esto no es un problema porque un símbolo es un objeto inmutable . Las cadenas son mutables.


(En respuesta al comentario a continuación)

En el artículo original, el valor no se almacena en un símbolo, se almacena en un hash. Considera esto:

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

Esto crea seis objetos en la memoria: cuatro objetos de cadena y dos objetos hash.

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

Esto solo crea cinco objetos en la memoria: un símbolo, dos cadenas y dos objetos hash.

anshul
fuente
Sin embargo, el ejemplo del enlace muestra los símbolos que contienen valores diferentes , pero el símbolo tiene el mismo nombre y la misma ubicación de memoria. Cuando salen , tienen valores diferentes , esa es la parte que no entiendo. ¿Seguramente deberían contener el mismo valor?
Kezzer
1
Acabo de hacer una edición para intentar explicar cómo todavía estoy confundido. Mi cerebro no puede calcular;)
Kezzer
48
Los símbolos no contienen valores, son valores. Los hash contienen valores.
Mladen Jablanović
5
Es el Hash(creado por {... => ...} en su código) el que almacena los pares clave / valor, no los Symbols mismos. La Symbols (por ejemplo, :symbolo :symo :ruby) son las claves en los pares. Sólo como parte de un Hash"señalan" algo.
James A. Rosen
1
El símbolo se usa como clave en el hash, no como valor, por eso pueden ser diferentes, es similar a usar key1 = 'ruby' y hash1 = {key1 => 'value' ...} hash2 = { clave1 => 'valor2' ...}.
Joshua Olson
53

Pude descifrar símbolos cuando lo pensaba así. Una cadena Ruby es un objeto que tiene varios métodos y propiedades. A la gente le gusta usar cadenas para claves, y cuando la cadena se usa para una clave, no se usan todos esos métodos adicionales. Así que hicieron símbolos, que son objetos de cadena con todas las funciones eliminadas, excepto la necesaria para que sea una buena clave.

Piense en los símbolos como cadenas constantes.

Segfault
fuente
2
Al leer las publicaciones, esta probablemente tenga más sentido para mí. : ruby ​​simplemente se almacena en algún lugar de la memoria, si uso "ruby" en algún lugar, luego "ruby" de nuevo en algún lugar, es solo una duplicación. Entonces, usar símbolos es una forma de reducir la duplicación de datos comunes. Como dices, cuerdas constantes. ¿Supongo que hay algún mecanismo subyacente que encontrará ese símbolo nuevamente para usar?
Kezzer
@Kezzer Esta respuesta es realmente buena y me parece correcta, pero su comentario dice algo diferente y es incorrecto o engañoso, su comentario habla de la duplicación de datos con cadenas, y esa es la razón de los símbolos, eso es incorrecto o engañoso. el símbolo varias veces no ocupará más espacio de memoria, pero también puede tenerlo para cadenas en muchos idiomas, por ejemplo, algunos lenguajes de programación si escribe "abc" y en otro lugar "abc", el compilador ve que es la misma cadena de valor y la almacena en el mismo lugar haciéndolo el mismo objeto, eso se llama internación de cadenas y c # hace eso.
barlop
Entonces, ¿es básicamente una versión increíblemente liviana de una cuerda?
stevec
34

El símbolo :rubyno contiene "red"ni "programming". El símbolo :rubyes solo el símbolo :ruby. Son sus hashes, patient1y patient2cada uno contiene esos valores, en cada caso apuntados por la misma clave.

Piénselo de esta manera: si va a la sala de estar la mañana de Navidad y ve dos cajas con una etiqueta que dice "Kezzer" en ellas. Uno tiene calcetines y el otro tiene carbón. No se va a confundir y preguntar cómo "Kezzer" puede contener tanto calcetines como carbón, aunque tenga el mismo nombre. Porque el nombre no contiene los (horribles) regalos. Solo los está señalando. Del mismo modo, :rubyno contiene los valores en su hash, solo los apunta.

jcdyer
fuente
2
Esta respuesta tiene mucho sentido.
Vass
Esto suena como una mezcla total de hashes y símbolos. Un símbolo no apunta a un valor, si quiere decir que lo hace cuando está en un hash, bueno, eso podría ser discutible, pero un símbolo no tiene que estar en un hash. Puede decir que mystring = :steveT el símbolo no apunta a nada. Una clave en un hash tiene un valor asociado y la clave podría ser un símbolo. Pero un símbolo no necesita estar en un hash.
barlop
27

Puede suponer que la declaración que ha realizado define el valor de un símbolo como algo diferente de lo que es. De hecho, un símbolo es simplemente un valor de cadena "internalizado" que permanece constante. Debido a que se almacenan usando un identificador de entero simple, se usan con frecuencia, ya que es más eficiente que administrar una gran cantidad de cadenas de longitud variable.

Tome el caso de su ejemplo:

patient1 = { :ruby => "red" }

Esto debe leerse como: "declarar una variable paciente1 y definirla como un Hash, y en esta tienda el valor 'rojo' debajo de la clave (símbolo 'rubí')"

Otra forma de escribir esto es:

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

puts patient1[:ruby]
# 'red'

Al realizar una asignación, no es de extrañar que el resultado que obtenga sea idéntico al que le asignó en primer lugar.

El concepto de símbolo puede ser un poco confuso ya que no es una característica de la mayoría de los otros idiomas.

Cada objeto String es distinto incluso si los valores son 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 con el mismo valor se refiere al mismo 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

La conversión de cadenas en símbolos asigna valores idénticos al mismo 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

Del mismo modo, la conversión de símbolo a cadena crea una cadena distinta cada vez:

[ :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

Puede pensar que los valores de símbolo se extraen de una tabla hash interna y puede ver todos los valores que se han codificado en símbolos mediante una simple llamada a un método:

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=, ...

A medida que defina nuevos símbolos, ya sea mediante la notación de dos puntos o utilizando .to_sym, esta tabla crecerá.

tadman
fuente
17

Los símbolos no son indicadores. No contienen valores. Los símbolos simplemente son . :rubyes el símbolo :rubyy eso es todo. No contiene un valor, no hace nada, solo existe como símbolo :ruby. El símbolo :rubyes un valor al igual que el número 1. No apunta a otro valor más que el número 1.

Arrojar
fuente
13
patient1.each_key {|key| puts key.to_s}

Entonces, ¿qué saldrá? ¿"rojo" o "programación"?

Tampoco generará "ruby".

Estás confundiendo símbolos y hashes. No están relacionados, pero son útiles juntos. El símbolo en cuestión es :ruby; no tiene nada que ver con los valores en el hash, y su representación interna de enteros siempre será la misma, y ​​su "valor" (cuando se convierte en una cadena) siempre será "ruby".

meagar
fuente
10

En breve

Los símbolos resuelven el problema de crear representaciones inmutables y legibles por humanos que también tienen la ventaja de ser más sencillas de buscar en tiempo de ejecución que las cadenas. Piense en ello como un nombre o etiqueta que se puede reutilizar.

Por qué: el rojo es mejor que el "rojo"

En los lenguajes dinámicos orientados a objetos se crean estructuras de datos anidadas complejas con referencias legibles. El hash es un caso de uso común en el que asigna valores a claves únicas, únicas, al menos, para cada instancia. No puede tener más de una clave "roja" por hash.

Sin embargo, sería más eficiente para el procesador usar un índice numérico en lugar de claves de cadena. Así que los símbolos se introdujeron como un compromiso entre velocidad y legibilidad. Los símbolos se resuelven mucho más fácilmente que la cadena equivalente. Al ser legibles por humanos y fáciles de resolver para el tiempo de ejecución, los símbolos son una adición ideal a un lenguaje dinámico.

Beneficios

Dado que los símbolos son inmutables, se pueden compartir en el tiempo de ejecución. Si dos instancias de hash tienen una necesidad lexicográfica o semántica común para un elemento rojo, el símbolo: rojo usaría aproximadamente la mitad de la memoria que la cadena "rojo" habría requerido para dos hash.

Dado que: el rojo siempre se resuelve en la misma ubicación en la memoria, se puede reutilizar en cien instancias de hash sin casi aumentar la memoria, mientras que el uso de "rojo" agregará un costo de memoria ya que cada instancia de hash necesitaría almacenar la cadena mutable en creación.

No estoy seguro de cómo Ruby realmente implementa símbolos / cadenas, pero claramente un símbolo ofrece menos sobrecarga de implementación en el tiempo de ejecución, ya que es una representación fija. Los símbolos más requieren un carácter menos para escribir que una cadena entre comillas y menos escritura es la búsqueda eterna de los verdaderos Rubyists.

Resumen

Con un símbolo como: rojo, obtiene la legibilidad de la representación de cadena con menos gastos generales debido al costo de las operaciones de comparación de cadenas y la necesidad de almacenar cada instancia de cadena en la memoria.

Mark Fox
fuente
4

Recomendaría leer el artículo de Wikipedia sobre tablas hash ; creo que te ayudará a hacerte una idea de lo que {:ruby => "red"}realmente significa.

Otro ejercicio que podría ayudarlo a comprender la situación: considere {1 => "red"}. Semánticamente, esto no significa "establecer el valor de 1a "red"", lo cual es imposible en Ruby. Más bien, significa "crear un objeto Hash y almacenar el valor "red"de la clave 1.

Greg Campbell
fuente
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

patient1y patient2ambos son hashes, está bien. :rubysin embargo es un símbolo. Si tuviéramos que generar lo siguiente:

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

Entonces, ¿qué saldrá? ¿"rojo" o "programación"?

Ninguno, por supuesto. La salida será ruby. Lo cual, por cierto, podría haberlo descubierto en menos tiempo del que le llevó escribir la pregunta, simplemente escribiéndola en IRB.

¿Por qué tendría que ser redo programming? Los símbolos siempre se evalúan por sí mismos. El valor del símbolo :rubyes el símbolo en :rubysí y la representación de cadena del símbolo :rubyes el valor de cadena "ruby".

[Por cierto: putssiempre convierte sus argumentos en cadenas, de todos modos. No hay necesidad de llamarlo to_s.]

Jörg W Mittag
fuente
No tengo IRB en la máquina actual, tampoco podría instalarlo, por lo tanto, mis disculpas por eso.
Kezzer
2
@Kezzer: No te preocupes, solo tenía curiosidad. A veces te sumerges tan profundamente en un problema que ya ni siquiera puedes ver las cosas más simples. Cuando básicamente corté y pegué su pregunta en IRB, me pregunté: "¿por qué no lo hizo él mismo?" Y no te preocupes, no eres el primero (ni serás el último) que pregunta "¿qué hace esta impresión?" Cuando la respuesta es "¡simplemente ejecútalo!" Por cierto: aquí está su IRB instantáneo, en cualquier lugar, en cualquier momento, sin necesidad de instalación: TryRuby.Org o Ruby-Versions.Net le brinda acceso SSH a todas las versiones de MRI jamás lanzadas + YARV + JRuby + Rubinius + REE.
Jörg W Mittag
Gracias, solo estoy jugando con él ahora. Sin embargo, todavía estoy un poco confundido, así que lo repaso de nuevo.
Kezzer
0

Soy nuevo en Ruby, pero creo (¿espero?) Que esta es una forma sencilla de verlo ...

Un símbolo no es una variable ni una constante. No representa ni apunta a un valor. Un símbolo ES un valor.

Todo lo que es, es una cadena sin el objeto por encima. El texto y solo el texto.

Así que esto:

"hellobuddy"

Es lo mismo que esto:

:hellobuddy

Excepto que no puede hacer, por ejemplo,: hellobuddy.upcase. Es el valor de la cadena y SOLO el valor de la cadena.

Asimismo, esto:

greeting =>"hellobuddy"

Es lo mismo que esto:

greeting => :hellobuddy

Pero, de nuevo, sin la sobrecarga del objeto de cadena.

Dave Munger
fuente
-1

Una manera fácil de entender esto es pensar, "¿y si estuviera usando una cuerda en lugar de un símbolo?

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

No es confuso en absoluto, ¿verdad? Estás usando "ruby" como clave en un hash .

"ruby"es un literal de cadena, por lo que ese es el valor. La dirección de memoria, o puntero, no está disponible para usted. Cada vez que invoca "ruby", está creando una nueva instancia de la misma, es decir, creando una nueva celda de memoria que contiene el mismo valor - "ruby".

El hash luego dice "¿cuál es mi valor clave? Oh, lo es "ruby". Luego asigna ese valor a" rojo "o" programación ". En otras palabras, :rubyno elimina la referencia a "red"o "programming". El hash se asigna :ruby a "red"o "programming".

Compare eso con si usamos símbolos

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

El valor de :rubyes también "ruby", efectivamente.

¿Por qué? Porque los símbolos son esencialmente constantes de cadena . Las constantes no tienen varias instancias. Es la misma dirección de memoria. Y una dirección de memoria tiene un cierto valor, una vez desreferenciada. Para los símbolos, el nombre del puntero es el símbolo y el valor desreferenciado es una cadena, que coincide con el nombre del símbolo, en este caso "ruby",.

Cuando está en un hash, no está usando el símbolo, el puntero, sino el valor deferencia. No estás usando :ruby, pero "ruby". Luego, el hash busca la clave "ruby", el valor es "red"o "programming", dependiendo de cómo haya definido el hash.

El cambio de paradigma y el concepto para llevar a casa es que el valor de un símbolo es un concepto completamente separado de un valor mapeado por un hash, dada una clave de ese hash.

ahnbizcad
fuente
¿Cuál es la falacia o el error en esta explicación, votantes en contra? curioso por aprender.
ahnbizcad
el hecho de que una analogía pueda resultar desagradable para algunos no significa que sea defectuosa.
ahnbizcad
2
No veo ningún error, necesariamente, pero es extremadamente difícil precisar lo que está tratando de decir en esta respuesta.
Ingeniería inversa
x se integra y conceptualiza de manera diferente según el contexto / entidad que realiza la interpretación. bastante simple.
ahnbizcad
revisó la respuesta. Gracias por la respuesta.
ahnbizcad