13 maneras de interpretar un símbolo de Ruby
Esta es una traducción de un artículo que leí hace mucho y que espero te sea útil. El artículo original fue escrito por Eric Kidd y se encuentra en blog “Random Hacks”:http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol.
Decidí traducir esto porque los símbolos son una de las cosas de Ruby que a mi me costo un poco de trabajo comprender, y si no tienes experiencia en esto, seguramente encontrarás algo útil aquí.
-
Programadores nuevos en Ruby con frecuencia preguntan, “¿Qué es, exactamente, un símbolo? ¿Y en que se diferencia de una cadena?” Ninguna respuesta funciona para todos, así que –con “disculpas a Wallace Stevens”:http://www.poets.org/viewmedia.php/prmMID/15746 – aquí hay 13 maneras de interpretar un símbolo en Ruby.
Un símbolo en Ruby es:
# …”el nombre de algo, no solo una masa de texto”:#1
# …”una etiqueta en una enumeración de forma libre”:#2
# …”una constante, un nombre único”:#3
# …”una cadena “internada””:#4
# …”un objeto de comparación O(1)”:#5
# …”un identificador de Lisp”:#6
# …”un identificador de Ruby”:#7
# …”la palabra clave para un argumento de palabra clave”:#8
# …”una excelente elección para una clave “hash””:#9
# …”como un OSType de Mac”:#10
# …”una fuga de memoria”:#11
# …”una forma astuta de almacenar una sola copia de una cadena”:#12
# …”un typedef de C llamado “ID””:#13
h3. 1. Un símbolo de Ruby es lo mismo que el nombre de algo, no solo una masa de texto
En Ruby, generalmente usamos símbolos cuando nos referimos a las cosas por su nombre:
[ruby]buscar_discurso(:gettysburg_address)[/ruby]
h6. (N. del T.: Gettysburg Address fue un discurso de Abraham Lincoln en 1863).
Pero para representar grandes trozos de texto, usaríamos cadenas:
[ruby]”Four score and seven years ago…”[/ruby]
h3. 2. Un símbolo de Ruby es una etiqueta en una enumeración de forma libre
En C++ (y muchos otros lenguajes), podemos usar “enumeraciones” para representar familias de constantes relacionadas:
[cpp]
enum EstadoBug { ABIERTO, CERRADO };
EstadoBug estado_original = ABIERTO;
EstadoBug estado_actual = CERRADO;
[/cpp]
Pero ya que Ruby es un lenguaje dinámico, no necesitamos preocuparnos por declarar un tipo *EstadoBug*, o estar al tanto de los valores legales. En lugar de eso, representamos los valores de la enumeración como símbolos:
[ruby]
estado_original = :abierto
estado_actual = :cerrado
[/ruby]
h3. 3. Un símbolo de Ruby es una constante, un nombre único
En Ruby, podemos cambiar el contenido de una cadena:
[ruby]”foo”[0] = ?b # “boo”[/ruby]
Pero no podemos cambiar el contenido de un símbolo:
[ruby]:foo[0] = ?b # Causa un error[/ruby]
Similarmente, podemos tener dos cadenas diferentes con el mismo contenido:
[ruby]
# El mismo contenido en la cadena, pero son diferentes objetos
“abierto”.object_id != “abierto”.object_id
[/ruby]
Sin embargo, dos símbolos con el mismo nombre son siempre el mismo objeto:
[ruby]
# Mismo nombre de símbolo, mismo objeto.
:abierto.object_id
[/ruby]
h3. 4. Un símbolo de Ruby es una cadena “internada”
En Ruby, podemos convertir una cadena a un símbolo usando *intern*:
[ruby]”foo”.intern # devuelve :foo[/ruby]
*intern* mantiene una tabla de _hashes_ que mapea cadenas con su símbolo correspondiente. La primera vez que *intern* ve una cadena, crea un nuevo símbolo y lo almacena en la tabla. La siguiente vez que ve una cadena, *intern* devuelve el objeto original.
Podríamos implementar nuestra propia versión de *Symbol* e *intern* de la siguiente manera:
We could implement our own version of Symbol and intern as follows:
[ruby]
class MiSimbolo
TABLA={}
def initialize(str) @str = str end
def to_s() @str end
def
self.object_id
end
end
class String
def mi_intern
tabla = MiSimbolo::TABLA
unless tabla.has_key?(self)
tabla[self] = MiSimbolo.new(self)
end
tabla[self]
end
end
“foo”.mi_intern
[/ruby]
h3. 5. Un símbolo de Ruby es un objeto con comparación O(1)
Para comparar dos cadenas, potencialmente tendremos que revisar cada caracter. Para dos cadenas de longitud N, esto tomara N+1 comparaciones (a lo que científicos en computación se refieren como “tiempo O(N)”).
[ruby]
def compara_cadenas str1, str2
return false if str1.length != str2.length
for i in 0…str1.length
return false if str1[i] != str2[i]
end
return true
end
compara_cadenas “foo”, “foo”
[/ruby]
Pero como cada aparición de *:foo* se refiere al mismo objeto, podemos comparar símbolos mirando sus IDs de objeto. Podemos hacer esto con una sola comparación (a lo que científicos en computación se refieren como “tiempo O(1)”).
[ruby]
def comparar_simbolos sym1, sym2
sym1.object_id
end
comparar_simbolos :foo, :foo
[/ruby]
h3. 6. Un símbolo de Ruby es un identificador Lisp
Los más antiguos ancestros de los símbolos de Ruby son los símbolos de Lisp. En Lisp, se usan símbolos para representar “identificadores” (nombres de variables y funciones) en un programa analizado. Digamos que tenemos un archivo llamado *doble.l* que contenga una sola función:
[code]
(defun doble (x)
(* x 2))
[/code]
Podemos analizar este archivo usando *read*:
[code]
(read “doble.l”)
;; Devuelve ‘(defun doble (x) (* x 2))
[/code]
Esto devuelve una lista anidada albergando los símbolos *defun*, *doble*, *, *x* (dos veces), y el número 2.
h3. 7. Un símbolo en Ruby es un identificador de Ruby
En Ruby, podemos buscar identificadores (nombres de variables, funciones, y constantes) mientras el programa se esta ejecutando. Esto típicamente se logra usando símbolos.
[ruby]
class Demo
# Lo que vamos a buscar por defecto.
DEFAULT = “Hola”
def initialize
@mensaje = DEFAULT
end
def habla() @mensaje end
# Usar símbolos para buscar identificadores.
def buscar_con_simbolos
[Demo.const_get(:DEFAULT),
method(:habla),
instance_variable_get(:@mensaje)]
end
end
Demo.new.buscar_con_simbolos
[/ruby]
h3. 8. Un símbolo en Ruby es una palabra clave para un argumento de palabra clave
Cuando pasamos argumentos de palabra clave a una función en Ruby, especificamos las palabras clave usando símbolos:
[ruby]
# Construir un URL para ‘bug’ usando Rails.
url_for :controller => ‘bug’,
:action => ’show’,
:id => bug.id
[/ruby]
h3. 9. Un símbolo en Ruby es una opción excelente para una clave de un _hash_
Comúnmente usamos símbolos para representar las claves en una tabla _hash_:
[ruby]
opciones = {}
opciones[:auto_guardar] = true
opciones[:mostrar_comentarios] = false
[/ruby]
h3. 10. Un símbolo en Ruby es como un OSType en Mac
El MacOS usa abreviaciones de cuatro caracteres para representar enumeraciones abiertas:
[code]
enum {
kTipoCarpetaSistema = ‘macs’,
kTipoCarpetaEscritorio = ‘desk’,
// …y así sucesivamente…
kTipoCarpetaPapelera = ‘trsh’
};
OSType carpeta = kTipoCarpetaSistema;
[/code]
En Ruby, usaríamos símbolos para el mismo propósito:
[ruby]
:carpeta_sistema
:carpeta_escritorio
:carpeta_papelera
[/ruby]
h3. 11. Un símbolo en Ruby es una fuga de memoria
Debido a la forma en la que los símbolos son almacenados en Ruby, “nunca pueden ser recolectados como basura”:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/164890. Así que si creamos 10,000 símbolos de un solo uso que jamás volveremos a usar, nunca recuperaremos la memoria.
Algunas “implementaciones de Scheme”:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/165011 usan una versión inteligente de *intern* que busca los símbolos usando una tabla de _hash_ débil. Esto permite que los símbolos sean recolectados sin destruir sus propiedades de unicidad.
h3. 12. Un símbolo en Ruby es una forma astuta de almacenar una sola copia de una cadena
(Para una idea similar, “ver este artículo”:http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols)
Digamos que estamos trabajando en un analizador de lenguaje natural que intenta comprender ordenes de desayunos. Tenemos un cuerpo de 30,000 enunciados que representan ordenes reales, e intentamos encontrar patrones.
Pero incluso aunque tenemos un gran número de enunciados, el vocabulario en si es muy limitado. ¡No queremos almacenar 15,000 copias de la palabra “tocino” en memoria! En lugar de eso, podemos usar símbolos para representar cada palabra:
[ruby]
cuerpo = [
[:yo, :quiero, :un, :poco, :de, :tocino],
[:yo, :quiero, :unos, :huevos],
[:dame, :un, :poco, :de, :tocino],
[:tocino, :crujiente],
# … 29,995 frases más …
[:un, :poco, :de, :pan, :tostado, :por, :favor]
]
[/ruby]
En los primeros días de la IA, muchos programadores Lisp usaban exactamente esta estrategia para representar texto.
h3. 13. Un símbolo en Ruby es un typedef en C llamado “ID”
Internamente, Ruby 1.8 representa símbolos usando el ID del tipo. Esto es un *typedef* para un entero sin signo. Un ID representa una entrada en la tabla de símbolos de Ruby.
[cpp]
typedef unsigned long ID;
[/cpp]
Algunas funciones interesantes relacionadas con símbolos incluyen:
[cpp]
// Almacenar una cadena de C en una tabla de símbolos.
ID rb_intern(const char *nombre);
// Convertir un ID a un objeto Symbol.
#define
// Convertir una cadena a un objeto Symbol.
VALUE rb_str_intern(VALUE s);
[/cpp]
h3. Otras explicaciones de símbolos de Ruby
Si ninguna de estas explicaciones te sirven, puedes probar suerte con alguna de las siguientes (en inglés):
# “Los símbolos no son cadenas inmutables”:http://onestepback.org/index.cgi/Tech/Ruby/SymbolsAreNotImmutableStrings.red
# “Usando símbolos por las razones incorrectas”:http://microjet.ath.cx/WebWiki/2005.12.27_UsingSymbolsForTheWrongReason.html
# “Otro blog más sobre símbolos en Ruby”:http://www.oreillynet.com/ruby/blog/2005/12/yet_another_blog_about_ruby_sy.html
# “Ahondando en símbolos de Ruby”:http://www.oreillynet.com/ruby/blog/2005/12/digging_into_ruby_symbols_1.html
# “Comprendiendo símbolos en Ruby”:http://glu.ttono.us/articles/2005/08/19/understanding-ruby-symbols