Por que o criador do Ruby optou por usar o conceito de símbolos?

15

tl; dr: Haveria uma definição independente de idioma dos Símbolos e uma razão para tê-los em outros idiomas?

Então, por que o criador do Ruby usou o conceito de symbolsna linguagem?

Eu pergunto isso da perspectiva de um programador não-rubi. Eu aprendi muitas outras línguas e não encontrei nenhuma delas, a necessidade de especificar se eu estava lidando ou não com o que Ruby chama symbols.

A principal questão é: o conceito de symbolsRuby existe para desempenho ou apenas algo que é necessário devido à maneira como a linguagem é escrita?

Um programa em Ruby seria mais leve e / ou mais rápido que seu, digamos, equivalente em Python ou Javascript? Se sim, seria por causa disso symbols?

Como uma das intenções de Ruby é ser fácil de ler e escrever para humanos, seus criadores não poderiam facilitar o processo de codificação implementando essas melhorias no próprio intérprete (como pode ser em outros idiomas)?

Parece que todo mundo quer saber apenas o que symbolssão e como usá-los, e não por que eles estão lá em primeiro lugar.

Yuri Ghensev
fonte
Scala tem símbolos, em cima da minha cabeça. Eu acho que muitos Lisps fazem.
D. Ben Knoble

Respostas:

17

O criador de Ruby, Yukihiro "Matz" Matsumoto, postou uma explicação sobre como Ruby foi influenciado por Lisp, Smalltalk, Perl (e a Wikipedia também diz Ada e Eiffel):

Ruby é uma linguagem projetada nas seguintes etapas:

  • use uma linguagem lisp simples (como a anterior ao CL).
  • remover macros, expressão s.
  • adicione um sistema de objetos simples (muito mais simples que o CLOS).
  • adicione blocos, inspirados em funções de ordem superior.
  • adicione métodos encontrados no Smalltalk.
  • adicione funcionalidade encontrada no Perl (de maneira OO).

Então, Ruby era originalmente um Lisp, em teoria.

Vamos chamá-lo de MatzLisp a partir de agora. ;-)

Em qualquer compilador, você gerenciará identificadores para funções, variáveis, blocos nomeados, tipos e assim por diante. Normalmente, você as armazena no compilador e as esquece no executável produzido, exceto quando você adiciona informações de depuração.

No Lisp, esses símbolos são recursos de primeira classe, hospedados em pacotes diferentes, o que significa que você pode adicionar símbolos novos em tempo de execução, vinculá-los a diferentes tipos de objetos. Isso é útil na metaprogramação, pois você pode ter certeza de que não terá colisões de nomes com outras partes do código.

Além disso, os símbolos são internados no tempo de leitura e podem ser comparados por identidade, que é uma maneira eficiente de ter novos tipos de valores (como números, mas abstratos). Isso ajuda a escrever código onde você usa valores simbólicos diretamente, em vez de definir seus próprios tipos de enumeração apoiados por números inteiros. Além disso, cada símbolo pode conter dados adicionais. É assim que, por exemplo, o Emacs / Slime pode anexar metadados do Emacs diretamente à lista de propriedades de um símbolo.

A noção de símbolo é central em Lisp. Dê uma olhada, por exemplo, no PAIP (Paradigmas de programação de inteligência artificial: estudos de caso em Common Lisp, Norvig) para obter exemplos detalhados.

coredump
fonte
5
Boa resposta. No entanto, eu discordo de Matz: nunca pensaria em chamar um idioma sem macros como dialeto cego. As instalações de metaprogramação em tempo de execução do lisp são exatamente o que confere a esse idioma seu poder impressionante, compensando sua gramática abissalmente simplista e inexpressiva.
cmaster - reinstate monica 15/09/16
11

Então, por que os criadores de Ruby tiveram que usar o conceito de symbolsna linguagem?

Bem, eles não precisavam "estritamente", eles escolheram. Além disso, observe que, estritamente falando, Symbols não fazem parte do idioma, eles fazem parte da biblioteca principal. Eles possuem sintaxe literal no nível do idioma, mas funcionariam da mesma forma se você tivesse que construí-los chamando Symbol::new.

Eu pergunto da perspectiva de um programador não-rubi tentando entender isso. Eu aprendi muitas outras línguas e não encontrei em nenhuma delas a necessidade de especificar se eu estava lidando ou não com o que Ruby chama symbols.

Você não disse o que são essas "muitas outras línguas", mas aqui está apenas um pequeno trecho de linguagem que possui um Symboltipo de dados como o Ruby:

Existem também outros idiomas que fornecem os recursos de Symbols de uma forma diferente. Em Java, por exemplo, os recursos de Ruby Strings são divididos em dois (na verdade três) tipos: Stringe StringBuilder/ StringBuffer. Por outro lado, as características de Ruby Symboltipo são dobradas para o Java StringTipo: Java Strings podem ser internado , strings literais e Strings que são o resultado de tempo de compilação expressões constantes avaliadas são automaticamente internado, geradas dinamicamente Strings podem ser internado chamando o String.internmétodo Um internado Stringem Java é exatamente como um Symbolem Ruby, mas não é implementado como um tipo separado, é apenas um estado diferente do que um JavaStringpode estar disponível. (Nota: nas versões anteriores do Ruby, String#to_symcostumava ser chamado String#interne esse método ainda existe hoje como um alias herdado.)

A principal questão poderia ser: o conceito de symbolsRuby existe como uma intenção de desempenho sobre si e outras linguagens,

Symbols são antes de tudo um tipo de dados com semântica específica . Essa semântica também possibilita implementar algumas operações de alto desempenho (por exemplo, testes rápidos de igualdade O (1)), mas esse não é o objetivo principal.

ou apenas algo que é necessário para existir devido à maneira como a linguagem é escrita?

Symbols não são necessários na linguagem Ruby, o Ruby funcionaria perfeitamente sem eles. Eles são puramente um recurso de biblioteca. Há exatamente um lugar no idioma vinculado a Symbols: uma defexpressão de definição de método é avaliada como Symboldenotando o nome do método que está sendo definido. No entanto, essa é uma alteração bastante recente, antes disso, o valor de retorno simplesmente não foi especificado. A RM simplesmente avaliou nil, Rubinius avaliou um Rubinius::CompiledMethodobjeto e assim por diante. Também seria possível avaliar para um UnboundMethod... ou apenas um String.

Um programa em Ruby seria mais leve e / ou mais rápido que seu, digamos, equivalente em Python ou Node? Se sim, seria por causa disso symbols?

Não tenho certeza do que você está perguntando aqui. O desempenho é principalmente uma questão de qualidade de implementação, não de linguagem. Além disso, o Node nem sequer é uma linguagem, é uma estrutura de E / S registrada para ECMAScript. Executando um script equivalente no IronPython e MRI, o IronPython provavelmente será mais rápido. Executando um script equivalente no CPython e JRuby + Truffle, é provável que o JRuby + Truffle seja mais rápido. Isso não tem nada a ver com Symbols, mas com a qualidade da implementação: o JRuby + Truffle possui um compilador de otimização agressiva, além de todo o mecanismo de otimização de uma JVM de alto desempenho, o CPython é um intérprete simples.

Como uma das intenções de Ruby é ser fácil de ler e escrever para humanos, seus criadores não poderiam facilitar o processo de codificação implementando essas melhorias no próprio intérprete (como pode ser em outros idiomas)?

No. Symbols não são uma otimização de compilador. Eles são um tipo de dados separado com semântica específica. Eles não são como os flonums do YARV , que são uma otimização interna privada para Floats. A situação não é a mesma que para Integer, Bignume Fixnum, o que deve ser um detalhe otimização interna privada invisível, mas infelizmente não é. (Isto é, finalmente, vai ser fixado em Ruby 2.4, que remove Fixnume Bignume folhas apenas Integer.)

Fazer isso da maneira que o Java faz, como um estado especial de Strings normal, significa que você sempre precisa ser cauteloso sobre se seus Strings estão ou não nesse estado especial e sob quais circunstâncias eles estão automaticamente nesse estado especial e quando não. Esse é um fardo muito maior do que simplesmente ter um tipo de dados separado.

Haveria uma definição independente de idioma dos símbolos e um motivo para tê-los em outros idiomas?

Symbolé um tipo de dados que denota o conceito de nome ou rótulo . Symbols são objetos de valor , imutáveis, geralmente imediatos (se a linguagem distingue uma coisa dessas), apátridas e sem identidade. Dois Symbols iguais são também garantidos idênticos, ou seja, dois Symbols iguais são na verdade o mesmo Symbol. Isso significa que igualdade de valor e igualdade de referência são a mesma coisa e, portanto, igualdade é eficiente e O (1).

Os motivos para tê-los em um idioma são realmente os mesmos, independentemente do idioma. Alguns idiomas dependem mais deles do que outros.

Na família Lisp, por exemplo, não há conceito de "variável". Em vez disso, você tem Symbols associados a valores.

Em linguagens com capacidades reflexivas ou introspectivos, Symbols muitas vezes são usados para indicar os nomes das entidades reflectidas nas APIs de reflexão, por exemplo, em Ruby, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methods, e Object#public_methodsretornar um Arrayde Symbols (embora eles poderiam muito bem retornar um Arrayde Methods). Object#public_sendusa um Symboldenotando o nome da mensagem a ser enviada como argumento (embora também aceite um String, Symbolseja mais semanticamente correto).

No ECMAScript, Symbols são um alicerce fundamental para tornar o ECMAScript seguro para os recursos no futuro. Eles também desempenham um grande papel na reflexão.

Jörg W Mittag
fonte
Átomos de Erlang foram tiradas diretamente do Prolog (Robert Virding me disse que em algum ponto)
Zachary K
2

Os símbolos são úteis no Ruby e você os verá em todo o código Ruby, porque cada símbolo é reutilizado toda vez que é referenciado. Isso é uma melhoria de desempenho em relação às cadeias, porque cada uso de uma cadeia que não é salva em uma variável cria um novo objeto na memória. Por exemplo, se eu usar a mesma sequência várias vezes como uma chave de hash:

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

A cadeia "a" é criada 101.000 vezes na memória. Se eu usasse um símbolo:

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

O símbolo :aainda é um objeto na memória. Isso torna os símbolos muito mais eficientes que as strings.

ATUALIZAÇÃO Aqui está uma referência (retirada da Codecademy ) que demonstra a diferença de desempenho:

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."

Aqui estão meus resultados para o meu MBP:

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

Há uma clara diferença no uso de strings x símbolos para apenas identificar chaves em um hash.

Keith Mattix
fonte
Não tenho certeza se é esse o caso. Eu esperaria que uma implementação Ruby execute o mesmo código várias vezes, sem analisar o código repetidamente para cada iteração. Mesmo que cada ocorrência lexical de "a"fato seja uma string nova, acho que no seu exemplo haverá exatamente duas "a"(e uma implementação pode até compartilhar a memória até que uma delas seja mutada). Para criar milhões de strings, você provavelmente precisará usar String.new ("a"). Mas eu não sou muito versado em Ruby, então talvez eu esteja errado.
Coredump10
1
Em uma das lições da Codecademy, eles geram uma referência para seqüências de caracteres versus símbolos, como no meu exemplo. Vou adicioná-lo à resposta.
precisa saber é o seguinte
1
Obrigado por adicionar a referência. Seu teste mostra o ganho esperado obtido usando símbolos em vez de cadeias, devido ao teste mais rápido na hashtable (identidade versus comparação de cadeias), mas não há como deduzir que as cadeias são alocadas a cada iteração. Eu adicionei uma versão com string_AZ[String.new("r")]para ver se isso faz diferença. Eu recebo 21ms para strings (versão original), 7ms com símbolos e 50ms com strings novos a cada vez. Então, eu diria que as strings não são alocadas tanto com a "r"versão literal .
Coredump10
1
Ah, então eu fiz mais algumas pesquisas e, no Ruby 2.1, as strings são de fato compartilhadas. Eu aparentemente perdi essa atualização; Obrigado por apontar isso. Voltando à pergunta original, acho que os dois benchmarks mostram a utilidade dos símbolos versus as strings.
Keith Mattix