Почему создатель Ruby решил использовать концепцию символов?

15

tl; dr: Будет ли определение символов, не зависящее от языка, и причина их использования на других языках?

Итак, почему создатель Ruby использовал концепцию symbolsв языке?

Я спрашиваю об этом с точки зрения неруби программиста. Я выучил много других языков и ни на одном из них не нашел необходимости указывать, имел ли я дело с тем, что называет Руби symbols.

Главный вопрос заключается в том, существует ли symbolsв Ruby концепция производительности или что-то, что нужно из-за того, как написан язык?

Будет ли программа на Ruby легче и / или быстрее, чем ее, скажем, аналог Python или Javascript? Если так, будет ли это из-за symbols?

Поскольку одним из намерений Ruby является простота чтения и записи для людей, разве его создатели не могли облегчить процесс кодирования, внедрив эти улучшения в самом интерпретаторе (как это может быть в других языках)?

Похоже, что все хотят знать только то, что symbolsесть и как их использовать, а не почему они там вообще.

Юрий Генсев
источник
У Скалы есть Символы, от макушки головы. Я думаю, что многие Лисп делают.
Д. Бен Кнобл

Ответы:

17

Создатель Ruby, Юкихиро "Matz" Мацумото, опубликовал объяснение того, как на Ruby повлияли Lisp, Smalltalk, Perl (а в Википедии также говорится об Ada и Eiffel):

Язык Ruby разработан с использованием следующих шагов:

  • возьмите простой язык LISP (например, до CL).
  • удалить макросы, s-выражение.
  • добавить простую объектную систему (намного проще, чем CLOS).
  • добавить блоки, вдохновленные функциями высшего порядка.
  • добавить методы, найденные в Smalltalk.
  • добавить функциональность, найденную в Perl (OO way).

Итак, изначально Ruby был Лиспом в теории.

Давайте теперь будем называть это MatzLisp. ;-)

В любом компиляторе вы будете управлять идентификаторами для функций, переменных, именованных блоков, типов и так далее. Обычно вы храните их в компиляторе и забываете о них в производимом исполняемом файле, за исключением случаев, когда вы добавляете отладочную информацию.

В Лиспе такие символы являются первоклассными ресурсами, размещенными в разных пакетах, что означает, что вы можете добавлять свежие символы во время выполнения, связывать их с различными типами объектов. Это полезно при метапрограммировании, потому что вы можете быть уверены, что у вас не будет конфликтов имен с другими частями кода.

Кроме того, символы интернируются во время чтения и могут сравниваться по тождеству, что является эффективным способом получения значений нового типа (например, чисел, но абстрактных). Это поможет в написании кода, в котором вы напрямую используете символические значения, вместо определения ваших собственных типов перечислений, подкрепленных целыми числами. Также каждый символ может содержать дополнительные данные. Вот как, например, Emacs / Slime может прикреплять метаданные из Emacs прямо в список свойств символа.

Понятие символа является центральным в Лиспе. Посмотрите, например, PAIP («Парадигмы программирования искусственного интеллекта: тематические исследования в Common Lisp, Norvig») для подробных примеров.

CoreDump
источник
5
Хороший ответ. Однако я не согласен с Мацем: я бы никогда не подумал о том, чтобы называть язык без макросов диалектом. Средства метапрограммирования во время выполнения lisp - это именно то, что дает этому языку его удивительную силу, восполняя его ужасно упрощенную, невыразительную грамматику.
cmaster - восстановить монику
11

Итак, почему создатели Ruby должны были использовать концепцию symbolsв языке?

Ну, они строго не «должны», они решили. Также обратите внимание, что, строго говоря, Symbols не являются частью языка, они являются частью базовой библиотеки. Они действительно имеют буквальный синтаксис языка на уровне, но они будут работать так же хорошо , если вы должны были построить их по телефону Symbol::new.

Я спрашиваю с точки зрения неруби программиста, пытающегося понять это. Я выучил много других языков и ни на одном из них не нашел необходимости указывать, имел ли я дело с тем, что называет Руби symbols.

Вы не сказали, что это за «множество других языков», но вот лишь небольшая выдержка из языков с Symbolтипом данных, подобным Ruby:

Есть и другие языки, которые предоставляют функции Symbols в другой форме. Например, в Java функции Ruby Stringделятся на два (фактически три) типа: Stringи StringBuilder/ StringBuffer. С другой стороны, функции типа Ruby Symbolобъединены в Stringтип Java : Java Stringмогут быть интернированы , литеральные строки и Strings, которые являются результатом вычисляемых во время компиляции константных выражений, автоматически интернируются, динамически генерируемые Strings могут интернироваться с помощью вызова String.internметод. Интернированный Stringв Java в точности такой же, как Symbolв Ruby, но он не реализован как отдельный тип, это просто другое состояние, что JavaStringможет быть в. (Примечание: в более ранних версиях Ruby String#to_symраньше вызывался, String#internи этот метод до сих пор существует как устаревший псевдоним.)

Главный вопрос может быть следующим: существует ли концепция symbolsв Ruby как стремление к производительности над собой и другими языками,

SymbolЭто прежде всего тип данных со специфической семантикой . Эта семантика также позволяет реализовать некоторые производительные операции (например, быстрое тестирование на равенство O (1)), но это не главная цель.

или просто то, что нужно для существования из-за того, как написан язык?

SymbolВ языке Ruby они вообще не нужны, без них Ruby прекрасно бы работал. Они являются чисто библиотечной функцией. В языке есть только одно место, которое связано с Symbols: defвыражение определения метода соответствует Symbolобозначению имени определяемого метода. Однако это довольно недавнее изменение, до этого возвращаемое значение просто оставалось неуказанным. МРТ просто оценивали nil, Рубиния оценивали до Rubinius::CompiledMethodобъекта и так далее. Также было бы возможно оценить к UnboundMethod... или просто String.

Будет ли программа на Ruby легче и / или быстрее, чем, скажем, аналог Python или Node? Если так, будет ли это из-за symbols?

Я не уверен, что вы спрашиваете здесь. Производительность в основном зависит от качества реализации, а не от языка. Кроме того, Node - это даже не язык, а интегрированная среда ввода / вывода для ECMAScript. Запуск эквивалентного скрипта на IronPython и MRI, скорее всего, IronPython будет быстрее. Запуск эквивалентного скрипта на CPython и JRuby + Truffle, JRuby + Truffle, вероятно, будет быстрее. Это не имеет ничего общего с Symbols, но с качеством реализации: JRuby + Truffle имеет агрессивно оптимизирующий компилятор, а также весь механизм оптимизации высокопроизводительной JVM, CPython - простой интерпретатор.

Поскольку одним из намерений Ruby является простота чтения и записи для людей, разве его создатели не могли упростить процесс кодирования, внедрив эти улучшения в самом интерпретаторе (как это может быть в других языках)?

Нет, Symbolэто не оптимизация компилятора. Это отдельный тип данных со специфической семантикой. Они не похожи на флаоны YARV , которые являются частной внутренней оптимизацией для Floats. Ситуация не такая, как для Integer, Bignumи Fixnum, которая должна быть невидимой частной внутренней оптимизацией, но, к сожалению, не так. (Это , наконец , будет исправлена в Рубине 2.4, который удаляет Fixnumи Bignumи листья просто Integer.)

Делать это так, как это делает Java, в качестве особого состояния нормальных Strings означает, что вам всегда нужно проявлять осторожность относительно того, Stringнаходятся ли ваши s в этом специальном состоянии и при каких обстоятельствах они автоматически находятся в этом специальном состоянии, а когда нет. Это гораздо большая нагрузка, чем просто наличие отдельного типа данных.

Будет ли определение символов, не зависящее от языка, и причина их использования на других языках?

Symbolэто тип данных, который обозначает концепцию имени или метки . SymbolЭто ценностные объекты , неизменяемые, обычно немедленные (если язык различает такие вещи), не имеющие состояния и не имеющие идентичности. Два Symbols, которые равны, также гарантированно будут идентичны, другими словами, два Symbols, которые равны, фактически являются одним и тем же Symbol. Это означает, что равенство значений и ссылочное равенство - это одно и то же, и, следовательно, равенство эффективно и O (1).

Причины, по которым они есть на языке, на самом деле одинаковы, независимо от языка. Некоторые языки полагаются на них больше, чем другие.

Например, в семействе Лисп нет понятия «переменная». Вместо этого вы Symbolсвязались со значениями.

В языках с отражающими или самосозерцательными возможностями, Symbols часто используется для обозначения названия отраженных сущностей в API , отражения, например , в Ruby, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methods, и Object#public_methodsвозвращать Arrayиз Symbolй (хотя они могли бы точно так же возвращать Arrayиз Methodс). Object#public_sendпринимает Symbolобозначение имени сообщения для отправки в качестве аргумента (хотя оно также принимает Stringи, Symbolболее семантически правильно).

В ECMAScript Symbols являются фундаментальным строительным блоком обеспечения безопасности ECMAScript в будущем. Они также играют большую роль в отражении.

Йорг Миттаг
источник
Эрланговские атомы были взяты непосредственно у Пролога (Роберт Вирдинг сказал мне, что в какой-то момент)
Захари К.
2

Символы полезны в Ruby, и вы увидите их по всему коду Ruby, потому что каждый символ используется повторно при каждой ссылке на него. Это улучшение производительности по сравнению со строками, потому что каждое использование строки, которая не сохранена в переменной, создает новый объект в памяти. Например, если я использую одну и ту же строку несколько раз в качестве хеш-ключа:

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

Строка «а» создается в памяти 101 000 раз. Если бы я использовал символ вместо:

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

Символ :a- это еще один объект в памяти. Это делает символы значительно более эффективными, чем строки.

ОБНОВЛЕНИЕ Вот тест (взятый из Codecademy ), который демонстрирует разницу в производительности:

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

Вот мои результаты для моего MBP:

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

Существует четкое различие в использовании строк и символов для простой идентификации ключей в хэше.

Кит Маттикс
источник
Я не уверен, если это так. Я ожидаю, что реализация Ruby будет выполнять один и тот же код несколько раз, не анализируя код снова и снова для каждой итерации. Даже если каждое лексическое вхождение "a"действительно является свежей строкой, я думаю, в вашем примере их будет ровно две "a"(и реализация может даже совместно использовать память, пока один из них не будет мутирован). Чтобы создать миллионы строк, вам, вероятно, потребуется использовать String.new ("a"). Но я не очень разбираюсь в Ruby, так что, возможно, я ошибаюсь.
coredump
1
В одном из уроков Codecademy они создают эталон для строк и символов, как и в моем примере. Я добавлю это к ответу.
Кит Мэттикс
1
Спасибо за добавление теста. Ваш тест показывает ожидаемый выигрыш, полученный при использовании символов вместо строк, из-за более быстрого теста в хеш-таблице (сравнение идентификаторов и строк), но мы не можем сделать вывод, что строки выделяются на каждой итерации. Я добавил версию string_AZ[String.new("r")]для того, чтобы увидеть, если это имеет значение. Я получаю 21 мс для строк (оригинальная версия), 7 мс с символами и 50 мс для свежих строк каждый раз. Так что я бы сказал, что строки с буквальной "r"версией выделяются не так сильно .
coredump
1
Ах, так что я сделал еще несколько копаний, и в Ruby 2.1 строки фактически являются общими. Я очевидно пропустил это обновление; Спасибо что подметил это. Возвращаясь к исходному вопросу, я думаю, что оба теста показывают полезность символов по сравнению со строками.
Кит Маттикс