Символы в Ruby

января 25, 2011  |  Published in Ruby, Основы  |  15 Comments

Еще раньше хотел написать эту статью, но забывал об этой своей идее. Сегодня вспомнил благодаря письму, пришедшему мне на email в котором меня попросили написать о том, что такое Символ в Ruby, где он используется и т.д. В общем пишу…

В интернетах наталкивался на несколько объяснений о том, что собой являют символы в Ruby. Все они начинали так: «Символ в Ruby — это экземпляр класса Symbol»… Ну да, символ — экземпляр класса Symbol, однако что нам это дает?

Из-за того, что в статьях посвященных символам все описывается туманно, или как в серьезных научных трудах, написанных одними бородатыми дядьками для других, не менее бородатых, дядек и процветает массовое непонимание этой концепции.

Чтобы понять, что есть вещь, необходимо понять, для чего эта вещь необходима. Чтобы писать меньше слов, я решил немножко визуализировать то, что хочу сказать, для этого я подготовил небольшой графический материал:

ruby symbol

Как видно из примера было создано два массива состоящих из хэшей. Единственное отличие между ними, это то, что в хэшах составляющих первый массив в качестве ключей используются строки, а во втором — символы. Мы также видим, что в первом случае было создано 4объекта — ключа, а во втором только два. Но почему так?

В этом и заключается основное отличие символов от строк. Дело в том, что строки «god save the queen», «god save the queen», «god save the queen» — это 3 разных объекта не смотря на то, что они абсолютно идентичны! В это же время символы :» god save the queen», :» god save the queen», :» god save the queen» — это всего один объект!

Можно сказать, что символы — это потомки строк, они абсолютно идентичны строкам с тем лишь отличием, что для одинаковых символов создается лишь один объект, а для одинаковых строк — по одному объекту на каждую строку. Начиная с Ruby 1.9 символы стали еще больше походить на строки, так как теперь символы также можно проверять на соответствие регулярному выражению.

Возникает вопрос: Зачем необходимо это различие? Как вы уже заметили, символы сами по себе практически не используются, они используются в основном в хэшах в качестве ключей. Это связано с тем, что ключ в виде символа так же удобен, как и ключ в виде строки, однако он лишен недостатка строк, который был описан выше.

Если у нас имеется массив пользователей из нескольких тысяч записей, где каждый Объект-пользователь, обладает такими свойствами, как id, name, login, password, reg_at, last_visit_at, address, то используя в качестве ключей — символы мы создадим всего 7 объектов — экземпляров класса Symbol, а используя в качестве ключей строки, мы создадим N*7 (где N — число объектов-пользователей), то есть несколько тысяч объектов только для того, чтобы хранить ключи в памяти компьютера (а ведь ключи — это побочные данные, которые нам необходимы только для удобства работы). Таким образом, приходим к выводу, что введение символов — это способ оптимизации в первую очередь потребления оперативной памяти компьютера.

Что еще отличает символы от строк?

Символы статичны! Вы не можете изменить символ (убедитесь сами, у символов нет многих методов свойственных строками типа .capitalize и т.д.), но легко можете изменить строку. Это отличие происходит от первого, описанного выше, которое в свою очередь произошло от необходимости оптимизации и такого свойства ключей ассоциативных массивов (хэшей), как неизменяемость. Вам когда-либо приходилось каким-либо образом изменять ключи в хэшах (я имею, введу сами ключи, а не структуру хэша)?

Даже если вы и теперь не поняли, что такое символ, то просто запомните: Символы используются только в качестве ключей и только их следует использовать в качестве ключей, если стоит выбор между использование символов и строк.

Некоторые особенности работы с символами

Работа с символами абсолютно идентична с работой с ключами любого-другого типа. Единственное отличие появилось в Ruby 1.9, когда для символов — ключей был предложен новый синтаксис:

# Вместо:
h = {:Vasya => {:age=>22, :height => 175, :weight => 80}}
# мы можем использовать более лаконичный синтаксис:
h = {Vasya: {age: 22, height: 175, weight:  80}}

Согласитесь, новый синтаксис гораздо удобнее!

Разрыв шаблона

После прочтения этой статьи вы наверняка стали убеждены в том, что символы используются только в качестве ключей, однако это не совсем так. Благодаря методу to_proc, ключи приобретают еще одно «магическое применение».

Метод to_proc позволяет конвертировать символ в блок кода. Как это «конвертировать символ в блок кода»? Я и сам не мог понять, почему это так называется, потому, что конвертировать символ в блок кода невозможно! Возможно, взять символ :capitalize и создать блок кода в котором будет вызываться метод capitalize. Ничего более!
Вот как выглядит метод to_proc:

def to_proc
  proc { |obj, *args| obj.send(self, *args) }
end

Вот пример использования:

a = :capitalize.to_proc  #=> #
a.call("hello world!") #=> Hello world!

По-сути, метод to_proc оборачивает метод в экземпляр объекта Proc, который в отличие от метода можно присвоить переменной. Это «оборачивание» происходит как видно из кода метода to_proc благодаря методу send, который просто вызывает метод соответствующий символу. Таким образом, в переменной a хранится процедура, которая в своем контексте вызывает метод и которая может быть вызвана при помощи метода call. То есть в переменной a хранится блок кода:

a = proc {|arg| arg.capitalize}

Ruby to_proc method
Вызов метода a.call(«hello») вызывает этот блок кода с параметром «hello». Все предельно просто!

Из всего этого становится понятно, что символы могут использоваться еще и как аргументы для вызова некоторых методов. Но абсолютно так же можно использовать и строки (не всегда, некоторые методы ожидают конкретно аргумента символьного типа). Пример:

attr_accessor :some_method
attr_accessor 'some_method'

Оба варианта абсолютно эквивалентны.

Преобразования символов в строки и обратно

Преобразовать символ в строку очень просто, остаточно использовать метод to_s:

:symbol.to_s  #=> "symbol"

Как же мы можем преобразовать строку в символ? — Все очень просто:

a = 'string'
b = eval(":#{a}") #=> :string

Правда, все просто? На самом деле это была шутка, все преобразование строки в символ выглядит еще проще благодаря методу to_sym:

'string'.to_sym #=> :string

Интересное поведение

Работая над этой статьей я обнаружил некоторое интересное поведение символов, оно заключается в том, что :symbol и :’symbol’ — это один и тот же символ, что проверяется очень просто:

:'symbol'.object_id #=> 332264
:symbol.object_id #=> 332264

Пока не знаю где это можно использовать и буду благодарен если кто-то поделится примером применения такого поведения.

UPD: Еще одна важная особенность символов о которой я забыл упомянуть

Как сравниваются строки? — Строки сравниваются путем сравнения их символов, т.е. строка А будет соответствовать строке B только если A[x] = B[x], т.е. все соответствующие символы равны.

Как же сравниваются символы? Символы преобразуются в численное представление (хеши). Далее символы сравниваются по сгенерированным для них числам. Такое сравнение работает гораздо более быстрее.  Что значит сравнение? — Это значит, например, поиск по ключу в ассоциативном массиве  состоящем из нескольких тысяч или миллионов пар ключь => значение. В этом случае выигрыш в скорости благодаря использованию символов будет весьма значительным.

Надеюсь, моя статья помогла вам разобраться с тем, что же такое символы в Ruby.

Лучшая благодарность автору блога — яхта ваши комментарии!

Tags: ,

Responses

  1. Alexey Artamonov says:

    января 25, 2011 at 23:49 (#)

    to_proc активно используется для таких конструкций
    ['hello', 'world'].map &:capitalize

  2. admin says:

    января 26, 2011 at 00:10 (#)

    Я об этом, кстати, упоминал вот в этом посте: http://rubydev.ru/2011/01/ruby_enumerable_2_unary_ampersand/

    Кстати, такое применение имеет большой недостаток в падении производительности, что-то ок 300%… В ближайшее время, если не забуду, напишу об этом более подробно.

  3. says:

    января 26, 2011 at 00:41 (#)

    [quote]все преобразование строки в символ выглядит еще проще благодаря методу to_sym[/quote]

    Чем это лучше/хуже использования intern?

    И, мне кажется, символы в качестве ключей должны работать значительно быстрей, чем строки. Не увидел сравнения…

  4. admin says:

    января 26, 2011 at 01:48 (#)

    На сколько я знаю, то intern и to_sym это синонимы, разницы в их использовании нет.Личто для меня to_sym — это привычка и, согласитесь, легче понять из названия, что делает метод to_sym, чем intern.

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

  5. Alexey says:

    января 26, 2011 at 11:55 (#)

    >> ['hello', 'world'].map &:capitalize
    такая конструкция уже не вызывает падения в производительности до 300% начиная с версии 1.9. Возможно есть замедление в пределах 10-20%.

  6. SuddenHead says:

    января 26, 2011 at 12:56 (#)

    Спасибо! Познавательно

  7. Alexey Artamonov says:

    января 26, 2011 at 17:27 (#)

    >> Benchmark.ms { (['hello', 'world']*1000).map &:capitalize }
    => 5.70416450500488
    >> Benchmark.ms { (['hello', 'world']*1000).map {|item| item.capitalize} }
    => 5.28812408447266

    ruby 1.8.7 (2010-04-19 patchlevel 253) [i686-linux], MBARI 0×8770, Ruby Enterprise Edition 2010.02

  8. admin says:

    января 26, 2011 at 17:34 (#)

    Алексей, спасибо за бенчмаркии!

  9. says:

    января 27, 2011 at 11:31 (#)

    Синтаксис :’symbol’ нужен для задания символов, содержащих не только буквы, цифры и подчёркивание. Например, :’symbol-with-dashes’ без кавычек написать не получится.

  10. admin says:

    января 27, 2011 at 14:22 (#)

    В первой половине статьи у меня используются символы:
    :»god save the queen», поэтому пытливые умы, которые еще не знакомы с символами должны были догадаться о такой возможности. Кроме того в разделе преобразования строки в символ приведен пример из котоого видно, что символ это (синтаксически) посто строка с двоеточием в начале =)

  11. yerlankusainov says:

    января 30, 2011 at 19:14 (#)

    Спасибо за статью, открыл для себя новые возможности:)

  12. Dmitriy Nesteryuk says:

    февраля 2, 2011 at 13:53 (#)

    Пишите,еще!!! Очень много интересного узнаю с ваших статей. Большое спасибо!!!

  13. Dmitriy Nesteryuk says:

    февраля 3, 2011 at 14:14 (#)

    Вы пишете:

    «Таким образом, приходим к выводу, что введение символов – это способ оптимизации в первую очередь потребления оперативной памяти компьютера.»

    А вот здесь пишут, что не стоит использовать символы

    »
    — старайтесь избегать использования Symbols, многие любят употреблять их как ключи в Hash. Проблема в том, что Symbols всегда имеет один и тот же id в контексте запущенного приложения Ruby, для того, чтобы можно было его использовать и при вызове методов в качестве параметра и при выборке по хешу. Обратная сторона медали — такие Symbols никогда не будут убраны сборщиком мусора, до самого завершения приложения;»

  14. admin says:

    февраля 3, 2011 at 20:52 (#)

    Представьте хэш на несколько миллионов пар, где используется всего 3-5-10-20 символов. 20 объектов в памяти это непомерно меньше, чем если будет загружено несколько миллионов ключей-строк. Да, символы не удаляются, пока приложение полностью не отработает, однако это не то, о чем следует заботится. Посмотрите хотя бы код Rails и приложений на Rails или любой другой библиотеки, семволы используются повсеместо и проблем не создают. Другое дело, если вы создадите ассоциативный массив в несколько миллионов пар, где каждый ключь будет уникальным символом. Я так понимаю именно об этой глупости предупреждает автор статьи. Кроме того, Ruby один из самых быстроразвивающихся языков с одним из самых активных комьюнити. Как мне кажется версия 1.9 это подготовка к 2.0, где мы увидим более проработанный интерпретатор с более умным и быстрым сборщиком муссора, и т.д. Ruby уже сейчас обгоняет PHP и Python3 и Perl по скорости уступая лишь Python 2. На ruby on rails написано огромное количество нагруженных проектов, которые вполне нормально функционируют. Оперативная память сейчас дешевая, за счет ее покупки можно легко компенсировать все утечки, если таковые будут иметься.

  15. ktulx says:

    ноября 26, 2013 at 22:54 (#)

    Большое спасибо за статью. Только начинаю изучать Ruby и из книг никак не мог понять именно область применения символов и, соответственно, не видел большой разницы между ними и строками. Теперь всё гораздо понятнее :)

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.