¿Por qué el creador de Ruby eligió usar el concepto de Símbolos?

15

TL; DR: ¿Habría una definición agnóstica del lenguaje de los Símbolos y una razón para tenerlos en otros idiomas?

Entonces, ¿por qué el creador de Ruby utilizó el concepto de symbolsen el lenguaje?

Pido esto desde la perspectiva de un programador no rubí. Aprendí muchos otros idiomas y no encontré en ninguno de ellos la necesidad de especificar si estaba tratando o no con lo que Ruby llama symbols.

La pregunta principal es, ¿existe el concepto de symbolsen Ruby para el rendimiento, o simplemente algo que se necesita debido a la forma en que se escribe el idioma?

¿Sería un programa en Ruby más ligero y / o más rápido que su, digamos, Python o Javascript? Si es así, ¿sería por symbols?

Dado que una de las intenciones de Ruby es ser fácil de leer y escribir para los humanos, ¿no podrían permitir que sus creadores facilitaran el proceso de codificación implementando esas mejoras en el propio intérprete (como podría ser en otros idiomas)?

Parece que todo el mundo quiere saber solo qué symbolsson y cómo usarlos, y no por qué están allí en primer lugar.

Yuri Ghensev
fuente
Scala tiene símbolos, en la parte superior de mi cabeza. Creo que muchos Lisps lo hacen.
D. Ben Knoble

Respuestas:

17

El creador de Ruby, Yukihiro "Matz" Matsumoto, publicó una explicación sobre cómo Ruby fue influenciado por Lisp, Smalltalk, Perl (y Wikipedia dice que Ada y Eiffel también):

Ruby es un lenguaje diseñado en los siguientes pasos:

  • tome un lenguaje lisp simple (como uno anterior a CL).
  • eliminar macros, s-expression.
  • agregue un sistema de objetos simple (mucho más simple que CLOS).
  • agregue bloques, inspirados en funciones de orden superior.
  • agregue métodos encontrados en Smalltalk.
  • agregar funcionalidad encontrada en Perl (en modo OO).

Entonces, Ruby era originalmente un Lisp, en teoría.

Llamémoslo MatzLisp de ahora en adelante. ;-)

En cualquier compilador, administrará identificadores para funciones, variables, bloques con nombre, tipos, etc. Por lo general, los almacena en el compilador y los olvida en el ejecutable producido, excepto cuando agrega información de depuración.

En Lisp, dichos símbolos son recursos de primera clase, alojados en diferentes paquetes, lo que significa que puede agregar símbolos nuevos en tiempo de ejecución, vincularlos a diferentes tipos de objetos. Esto es útil cuando se metaprograma porque puede estar seguro de que no tendrá colisiones de nombres con otras partes del código.

Además, los símbolos son internados en el momento de la lectura y se pueden comparar por identidad, que es una forma eficiente de tener nuevos tipos de valores (como números, pero abstractos). Esto ayuda a escribir código donde usa valores simbólicos directamente, en lugar de definir sus propios tipos de enumeración respaldados por enteros. Además, cada símbolo puede contener datos adicionales. Así es como, por ejemplo, Emacs / Slime puede adjuntar metadatos de Emacs directamente a la lista de propiedades de un símbolo.

La noción de símbolo es central en Lisp. Eche un vistazo, por ejemplo, a PAIP (Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp, Norvig) para ver ejemplos detallados.

volcado de memoria
fuente
55
Buena respuesta. Sin embargo, no estoy de acuerdo con Matz: nunca pensaría en llamar a un idioma sin macros un dialecto lisp. Las funciones de metaprogramación de tiempo de ejecución de lisp son precisamente lo que le da a este lenguaje su increíble poder, compensando su gramática abismalmente simplista e inexpresiva.
cmaster - reinstalar a monica el
11

Entonces, ¿por qué los creadores de Ruby tuvieron que usar el concepto symbolsen el lenguaje?

Bueno, ellos estrictamente "no tenían que", decidieron hacerlo. Además, tenga en cuenta que estrictamente hablando Symbols no son parte del lenguaje, son parte de la biblioteca central. Ellos no tienen una sintaxis literal de nivel de idioma, sino que funcionarían igual de bien si había que construir con ellos llamando Symbol::new.

Pregunto desde la perspectiva de un programador que no sea ruby ​​tratando de entenderlo. Aprendí muchos otros idiomas y no encontré en ninguno de ellos la necesidad de especificar si estaba tratando o no con lo que Ruby llama symbols.

No dijiste cuáles son esos "muchos otros idiomas", pero aquí hay un pequeño extracto de idiomas que tienen un Symboltipo de datos como el de Ruby:

También hay otros lenguajes que proporcionan las características de Symbols en una forma diferente. En Java, por ejemplo, las características de Ruby's Stringse dividen en dos (en realidad tres) tipos: Stringy StringBuilder/ StringBuffer. Por otro lado, las características del Symboltipo de Ruby se pliegan en el Stringtipo de Java : los Strings de Java pueden ser internados , las cadenas literales y las Strings que son el resultado de expresiones constantes evaluadas en tiempo de compilación se internan automáticamente, los Strings generados dinámicamente se pueden internar llamando El String.internmétodo Un interno Stringen Java es exactamente como un Symbolen Ruby, pero no está implementado como un tipo separado, es solo un estado diferente que un JavaStringpuede estar en. (Nota: en versiones anteriores de Ruby, String#to_symsolía llamarse String#interny ese método todavía existe hoy en día como un alias heredado).

La pregunta principal podría ser: ¿ symbolsexiste el concepto de en Ruby como una intención de rendimiento sobre sí mismo y otros idiomas,

Symbols son ante todo un tipo de datos con semántica específica . Estas semánticas también permiten implementar algunas operaciones de rendimiento (por ejemplo, pruebas rápidas de igualdad O (1)), pero ese no es el objetivo principal.

o simplemente algo que se necesita para existir debido a la forma en que se escribe el idioma?

Symbols no son necesarios en absoluto en el lenguaje Ruby, Ruby funcionaría bien sin ellos. Son puramente una función de biblioteca. Hay exactamente un lugar en el lenguaje que está vinculado a Symbols: una defexpresión de definición de método se evalúa como Symboldenotando el nombre del método que se está definiendo. Sin embargo, ese es un cambio bastante reciente, antes de eso, el valor de retorno simplemente se dejó sin especificar. MRI simplemente evaluó a nil, Rubinius evaluó a un Rubinius::CompiledMethodobjeto, y así sucesivamente. También sería posible evaluar a un UnboundMethod... o simplemente a String.

¿Sería un programa en Ruby más ligero y / o más rápido que su, digamos, Python o Node homólogo? Si es así, ¿sería por symbols?

No estoy seguro de lo que estás preguntando aquí. El rendimiento es principalmente una cuestión de calidad de implementación, no de lenguaje. Además, Node ni siquiera es un lenguaje, es un marco de E / S creado para ECMAScript. Al ejecutar un script equivalente en IronPython y MRI, es probable que IronPython sea más rápido. Al ejecutar un script equivalente en CPython y JRuby + Truffle, es probable que JRuby + Truffle sea más rápido. Esto no tiene nada que ver con Symbols, sino con la calidad de la implementación: JRuby + Truffle tiene un compilador de optimización agresiva, además de toda la maquinaria de optimización de una JVM de alto rendimiento, CPython es un intérprete simple.

Dado que una de las intenciones de Ruby es ser fácil de leer y escribir para humanos, ¿no podrían sus creadores facilitar el proceso de codificación mediante la implementación de esas mejoras en el propio intérprete (como podría ser en otros idiomas)?

No. Symbols no son una optimización del compilador. Son un tipo de datos separado con una semántica específica. No son como los flonums de YARV , que son una optimización interna privada para Floats. La situación no es la misma que para Integer, Bignumy Fixnum, que debería ser un detalle de optimización interna privada invisible, pero desafortunadamente no lo es. (Esto finalmente va a ser fijado en Ruby 2.4, que elimina Fixnumy Bignumy las hojas sólo Integer.)

Hacerlo de la manera en que lo hace Java, ya que un estado especial de Strings normal significa que siempre debe ser cauteloso acerca de si sus Strings están en ese estado especial y bajo qué circunstancias están automáticamente en ese estado especial y cuándo no. Esa es una carga mucho más alta que simplemente tener un tipo de datos separado.

¿Habría una definición agnóstica del lenguaje de los símbolos y una razón para tenerlos en otros idiomas?

Symboles un tipo de datos que denota el concepto de nombre o etiqueta . SymbolLos s son objetos de valor , inmutables, generalmente inmediatos (si el lenguaje distingue tal cosa), sin estado y sin identidad. SymbolTambién se garantiza que dos s que son iguales son idénticos, en otras palabras, dos Symbols que son iguales son en realidad el mismo Symbol. Esto significa que la igualdad de valores y la igualdad de referencia son lo mismo, y por lo tanto, la igualdad es eficiente y O (1).

Los motivos para tenerlos en un idioma son realmente los mismos, independientemente del idioma. Algunos idiomas dependen más de ellos que otros.

En la familia Lisp, por ejemplo, no existe el concepto de "variable". En cambio, tiene Symbols asociados a valores.

En las lenguas con las capacidades de reflexión o introspección, Symbols se utilizan a menudo para referirse a los nombres de entidades reflejadas en el API de reflexión, por ejemplo, en Rubí, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methods, y Object#public_methodsdevolver una Arrayde Symbols (aunque podrían igual de bien devolver una Arrayde Methods). Object#public_sendtoma un Symboldenotando el nombre del mensaje para enviarlo como argumento (aunque también acepta un String, Symboles más semánticamente correcto).

En ECMAScript, los Symbols son un componente fundamental para hacer que ECMAScript sea seguro en el futuro. También juegan un papel importante en la reflexión.

Jörg W Mittag
fuente
Los átomos de Erlang fueron tomados directamente de Prolog (Robert Virding me dijo eso en algún momento)
Zachary K
2

Los símbolos son útiles en Ruby, y los verá en todo el código Ruby porque cada símbolo se reutiliza cada vez que se hace referencia a él. Esta es una mejora del rendimiento sobre las cadenas porque cada uso de una cadena que no se guarda en una variable crea un nuevo objeto en la memoria. Por ejemplo, si uso la misma cadena varias veces como una clave hash:

my_hash = {"a" => 1, "b" => 2, "c" => 3}
100_000.times { |i| puts my_hash["a"] }

La cadena "a" se crea 101,000 veces en la memoria. Si usé un símbolo en su lugar:

my_hash = {a: 1, b: 2, c: 3}
100_000.times { |i| puts my_hash[:a] }

El símbolo :asigue siendo un objeto en la memoria. Esto hace que los símbolos sean mucho más eficientes que las cadenas.

ACTUALIZACIÓN Aquí hay un punto de referencia (tomado de Codecademy ) que demuestra la diferencia de rendimiento:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  100_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  100_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

Aquí están mis resultados para mi MBP:

String time: 0.1254125550040044 seconds.
Symbol time: 0.07360960397636518 seconds.

Hay una clara diferencia en el uso de cadenas frente a símbolos solo para identificar claves en un hash.

Keith Mattix
fuente
No estoy seguro si este es el caso. Esperaría que una implementación de Ruby ejecute el mismo código varias veces, sin analizar el código una y otra vez para cada iteración. Incluso si cada ocurrencia léxica de "a"hecho es una cadena nueva, creo que en su ejemplo habrá exactamente dos "a"(y una implementación podría incluso compartir la memoria hasta que una de ellas mute). Para crear millones de cadenas, probablemente necesite usar String.new ("a"). Pero no estoy bien versado en Ruby, así que tal vez estoy equivocado.
coredump
1
En una de las lecciones de Codecademy, generan un punto de referencia para cadenas frente a símbolos, al igual que mi ejemplo. Lo agregaré a la respuesta.
Keith Mattix
1
Gracias por agregar el punto de referencia. Su prueba muestra la ganancia esperada obtenida mediante el uso de símbolos en lugar de cadenas, debido a una prueba más rápida en la tabla hash (comparación de identidad frente a cadena), pero no hay forma de deducir que las cadenas se asignan en cada iteración. Agregué una versión con el string_AZ[String.new("r")]fin de ver si eso hace la diferencia. Obtengo 21 ms para cadenas (versión original), 7 ms con símbolos y 50 ms con cadenas nuevas cada vez. Entonces diría que las cadenas no se asignan tanto con la "r"versión literal .
coredump
1
Ah, entonces cavé un poco más, y en Ruby 2.1, las cadenas de hecho se comparten. Aparentemente me perdí esa actualización; gracias por señalar eso. Volviendo a la pregunta original, creo que ambos puntos de referencia muestran la utilidad de los símbolos frente a las cadenas.
Keith Mattix