Как понимать символы в Ruby

85

Несмотря на то, что я прочитал « Общие сведения о символах Ruby », меня все еще смущает представление данных в памяти, когда дело доходит до использования символов. Если символ, два из которых содержатся в разных объектах, существуют в одной и той же области памяти, то как же они содержат разные значения? Я ожидал, что одна и та же ячейка памяти будет содержать такое же значение.

Это цитата из ссылки:

В отличие от строк, одноименные символы инициализируются и существуют в памяти только один раз во время сеанса работы с ruby.

Я не понимаю, как ему удается различать значения, содержащиеся в одном месте памяти.

Рассмотрим этот пример:

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1и patient2оба являются хешами, это нормально. :rubyоднако это символ. Если бы нам нужно было вывести следующее:

patient1.each_key {|key| puts key.to_s}

Тогда что будет на выходе? "red", или "programming"?

Забыв на секунду о хэшах, я думаю, что символ - это указатель на значение. У меня есть следующие вопросы:

  • Могу ли я присвоить значение символу?
  • Является ли символ просто указателем на переменную со значением в ней?
  • Если символы глобальны, означает ли это, что символ всегда указывает на что-то одно?
Кеззер
источник
1
Будет выведено ": ruby", потому что вы печатаете символ. Если вы скажете puts patient1[:ruby], он напечатает «красный», если вы скажете puts patient2[:ruby], он напечатает «программирование».
РОСС
1
Символ НЕ является указателем на значение. Внутренне символ - это просто целое число.
akuhn

Ответы:

62

Учти это:

x = :sym
y = :sym
(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__) # => true

x = "string"
y = "string"
(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__) # => false

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


(В ответ на комментарий ниже)

В исходной статье значение хранится не в символе, а в хэше. Учти это:

hash1 = { "string" => "value"}
hash2 = { "string" => "value"}

Это создает шесть объектов в памяти - четыре строковых объекта и два хэш-объекта.

hash1 = { :symbol => "value"}
hash2 = { :symbol => "value"}

Это создает в памяти только пять объектов - один символ, две строки и два объекта хеширования.

Аншул
источник
Однако пример в ссылке показывает символы, содержащие разные значения, но символ имеет то же имя и тот же участок памяти. Когда они выводятся, у них разные значения, этого я не понимаю. Неужто они должны содержать одно и то же значение?
Kezzer
1
Я только что отредактировал, чтобы попытаться объяснить, почему я все еще запутался. Мой мозг не умеет вычислять;)
Кеззер
48
Символы не содержат значений, это значения. Хеши содержат значения.
Младен Ябланович
5
Это Hash(созданный {... => ...} в вашем коде), который хранит пары ключ / значение, а не Symbolсами s. Символы Symbols (например, :symbolили :symили :ruby) - это ключи в парах. Только как часть Hashони «указывают» на что-либо.
Джеймс А. Розен,
1
Символ используется в качестве ключа в хэше, а не в качестве значения, поэтому они могут быть разными, это похоже на использование выражения key1 = 'ruby' и hash1 = {key1 => 'value' ...} hash2 = { ключ1 => 'значение2' ...}.
Джошуа Олсон
53

Когда я думал об этом вот так, я мог гробить символы. Строка Ruby - это объект, у которого есть набор методов и свойств. Людям нравится использовать строки для ключей, а когда строка используется для ключа, все эти дополнительные методы не используются. Таким образом, они создали символы, которые представляют собой строковые объекты со всеми удаленными функциями, за исключением того, что необходимо для того, чтобы быть хорошим ключом.

Просто думайте о символах как о постоянных строках.

Segfault
источник
2
Читая посты, я думаю, что этот, вероятно, имеет наибольший смысл. : ruby ​​просто где-то хранится в памяти, если где-то я использую "рубин", то снова где-то "рубин", это просто дублирование. Таким образом, использование символов - это способ уменьшить дублирование общих данных. Как вы говорите, постоянные строки. Думаю, есть какой-то базовый механизм, который снова найдет этот символ для использования?
Kezzer 05
@Kezzer Этот ответ действительно хорош и кажется мне правильным, но ваш комментарий говорит о другом и неверен или вводит в заблуждение, ваш комментарий говорит о дублировании данных со строками и о том, что это причина символов, что неверно или вводит в заблуждение.Да, используя символ несколько раз не будет занимать больше места в памяти, но вы можете использовать это и для строк на многих языках, например, на некоторых языках программирования, если вы напишете «abc», а в другом месте «abc», компилятор увидит ту же строку значения и сохранит ее в том же месте, делая это тем же объектом, что называется интернированием строк, и C # делает это.
barlop
Значит, это невероятно легкая версия струны?
Stevec
34

Символ :rubyне содержит "red"или "programming". Символ :ruby- это просто символ :ruby. Это ваши хэши, patient1и patient2каждый из них содержит эти значения, в каждом случае на которые указывает один и тот же ключ.

Подумайте об этом так: если вы войдете в гостиную рождественским утром и увидите две коробки с биркой, на которой написано «Кеззер». У одного есть носки, а у другого - уголь. Вы не запутаетесь и не спросите, как «Кеззер» может содержать и носки, и уголь, хотя это одно и то же название. Потому что в названии нет (дерьмовых) подарков. Он просто указывает на них. Точно так :rubyже не содержит значений в вашем хэше, он просто указывает на них.

jcdyer
источник
2
Этот ответ имеет смысл.
Васс
Это звучит как полная смесь хэшей и символов. Символ не указывает на значение, если вы хотите сказать, что он указывает в хеше, что ж, это может быть спорным, но символ не обязательно должен быть в хеше. Можно сказать, mystring = :steveT что символ ни на что не указывает. Ключ в хэше имеет связанное значение, и ключ может быть символом. Но символ не обязательно должен быть хешем.
barlop
27

Вы можете предположить, что сделанное вами объявление определяет значение символа как нечто иное, чем то, чем оно является. Фактически, символ - это просто «внутреннее» строковое значение, которое остается постоянным. Поскольку они хранятся с использованием простого целочисленного идентификатора, они часто используются, поскольку это более эффективно, чем управление большим количеством строк переменной длины.

Рассмотрим ваш пример:

patient1 = { :ruby => "red" }

Это следует читать как: «объявить переменную пациента1 и определить ее как хэш, и в этом хранилище значение« красный »под ключом (символ« рубин »)»

Другой способ написать это:

patient1 = Hash.new
patient1[:ruby] = 'red'

puts patient1[:ruby]
# 'red'

Когда вы выполняете задание, неудивительно, что результат, который вы получаете, идентичен тому, что вы ему изначально присвоили.

Концепция символов может немного сбивать с толку, поскольку она не характерна для большинства других языков.

Каждый объект String отличается, даже если значения идентичны:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148099960
# "foo" 2148099940
# "foo" 2148099920
# "bar" 2148099900
# "bar" 2148099880
# "bar" 2148099860

Каждый символ с одинаковым значением относится к одному и тому же объекту:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

Преобразование строк в символы отображает одинаковые значения в один и тот же уникальный символ:

[ "foo", "foo", "foo", "bar", "bar", "bar" ].each do |v|
  v = v.to_sym
  puts v.inspect + ' ' + v.object_id.to_s
end

# :foo 228508
# :foo 228508
# :foo 228508
# :bar 228668
# :bar 228668
# :bar 228668

Точно так же преобразование из символа в строку создает каждый раз отдельную строку:

[ :foo, :foo, :foo, :bar, :bar, :bar ].each do |v|
  v = v.to_s
  puts v.inspect + ' ' + v.object_id.to_s
end

# "foo" 2148097820
# "foo" 2148097700
# "foo" 2148097580
# "bar" 2148097460
# "bar" 2148097340
# "bar" 2148097220

Вы можете думать о значениях символов как о взятых из внутренней хеш-таблицы, и вы можете видеть все значения, которые были закодированы в символы, используя простой вызов метода:

Symbol.all_values

# => [:RUBY_PATCHLEVEL, :vi_editing_mode, :Separator, :TkLSHFT, :one?, :setuid?, :auto_indent_mode, :setregid, :back, :Fail, :RET, :member?, :TkOp, :AP_NAME, :readbyte, :suspend_context, :oct, :store, :WNOHANG, :@seek, :autoload, :rest, :IN_INPUT, :close_read, :type, :filename_quote_characters=, ...

По мере того, как вы определяете новые символы либо с помощью двоеточия, либо с помощью .to_sym, эта таблица будет расти.

тадман
источник
17

Символы не являются указателями. Они не содержат значений. Символы просто есть . :rubyэто символ, :rubyи это все, что ему нужно. Он не содержит значения, ничего не делает , он просто существует как символ :ruby. Символ :ruby- это такое же значение, как и число 1. Оно не указывает на другое значение больше, чем число 1.

Чак
источник
13
patient1.each_key {|key| puts key.to_s}

Тогда что будет на выходе? «красный» или «программирование»?

И "рубин" тоже не будет.

Вы путаете символы и хеши. Они не связаны между собой, но вместе полезны. Символ в вопросе :ruby; он не имеет ничего общего со значениями в хэше, и его внутреннее целочисленное представление всегда будет одним и тем же, и его «значение» (при преобразовании в строку) всегда будет «рубиновым».

Meagar
источник
10

Короче говоря

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

Почему: красный лучше, чем «красный»

В динамических объектно-ориентированных языках вы создаете сложные вложенные структуры данных с читаемыми ссылками. Хэш является общим Прецедент , где карта значений уникальных ключей - уникальных, по крайней мере, в каждом конкретном случае. У вас не может быть более одного «красного» ключа на хэш.

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

Льготы

Поскольку символы неизменяемы, они могут использоваться во время выполнения. Если два экземпляра хэша имеют общую лексикографическую или семантическую потребность в красном элементе, символ: red будет использовать примерно половину памяти, которую строка «red» потребовала бы для двух хешей.

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

Не уверен, как Ruby на самом деле реализует символы / строку, но очевидно, что символ предлагает меньше накладных расходов на реализацию во время выполнения, поскольку это фиксированное представление. Для ввода плюсовых символов требуется на один символ меньше, чем для строки в кавычках, а меньшее количество набираемых символов - вечное стремление истинных рубистов.

Резюме

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

Марк Фокс
источник
4

Я бы рекомендовал прочитать статью в Википедии о хэш-таблицах - я думаю, это поможет вам понять, что на {:ruby => "red"}самом деле означает.

Еще одно упражнение, которое может помочь вам понять ситуацию: подумайте {1 => "red"}. Семантически это не означает «установить значение 1для "red"», что невозможно в Ruby. Скорее, это означает «создать объект Hash и сохранить значение "red"ключа 1.

Грег Кэмпбелл
источник
3
patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

patient1.each_key {|key| puts key.object_id.to_s}
3918094
patient2.each_key {|key| puts key.object_id.to_s}
3918094

patient1и patient2оба являются хешами, это нормально. :rubyоднако это символ. Если бы нам нужно было вывести следующее:

patient1.each_key {|key| puts key.to_s}

Тогда что будет на выходе? «красный» или «программирование»?

Конечно, ни то, ни другое. На выходе будет ruby. Что, кстати, вы могли бы выяснить за меньшее время, чем вам потребовалось, чтобы ввести вопрос, просто набрав его вместо этого в IRB.

Почему бы это быть redили programming? Символы всегда оценивают сами по себе. Значение символа :ruby- это :rubyсам символ , а строковое представление символа :ruby- это строковое значение "ruby".

[BTW: putsв любом случае всегда преобразует свои аргументы в строки. В этом нет необходимости to_s.]

Йорг В. Миттаг
источник
У меня нет IRB на текущей машине, и я не смогу установить его, поэтому приношу свои извинения за это.
Kezzer
2
@Kezzer: Не беспокойся, мне было просто любопытно. Иногда вы так глубоко погружаетесь в проблему, что больше не видите даже самых простых вещей. Когда я в основном вырезал и вставил ваш вопрос в IRB, я просто подумал: «Почему он сам этого не сделал?» И не волнуйтесь, вы не первый (и не последний), кто спрашивает «что это за отпечаток», когда ответ - «просто запустите!» Кстати: вот ваш мгновенный IRB в любом месте, в любое время, установка не требуется: TryRuby.Org или Ruby-Versions.Net дает вам SSH-доступ ко всем версиям MRI, когда-либо выпущенным + YARV + JRuby + Rubinius + REE.
Jörg W Mittag
Спасибо, просто поиграю с этим сейчас. Я все еще немного сбит с толку, поэтому повторюсь еще раз.
Kezzer
0

Я новичок в Ruby, но думаю (надеюсь?) Это простой способ взглянуть на это ...

Символ не является переменной или константой. Он не заменяет значение и не указывает на него. Символ - это ценность.

Все, что это есть, - это строка без накладных расходов на объект. Текст и только текст.

Итак, это:

"hellobuddy"

То же самое:

:hellobuddy

За исключением того, что вы не можете, например,: hellobuddy.upcase. Это строковое значение и ТОЛЬКО строковое значение.

Точно так же это:

greeting =>"hellobuddy"

То же самое:

greeting => :hellobuddy

Но, опять же, без накладных расходов на строковый объект.

Дэйв Мангер
источник
-1

Один простой способ осознать это - подумать: «А что, если бы я использовал строку, а не символ?

patient1 = { "ruby" => "red" }
patient2 = { "ruby" => "programming" }

Это совсем не сбивает с толку, правда? Вы используете «рубин» в качестве ключа в хэше .

"ruby"- строковый литерал, так что это значение. Адрес памяти или указатель вам недоступен. Каждый раз, когда вы вызываете "ruby", вы создаете его новый экземпляр, то есть создаете новую ячейку памяти, содержащую то же значение - "ruby".

Затем хеш идет «какое у меня ключевое значение? О, это "ruby". Затем отображает это значение на« красный »или« программирование ». Другими словами, :rubyне разыменовывается на "red"или "programming". Хеш сопоставляется :ruby с "red"или "programming".

Сравните это с использованием символов

patient1 = { :ruby => "red" }
patient2 = { :ruby => "programming" }

Ценность :rubyтакже очень "ruby"эффективна.

Зачем? Потому что символы по сути являются строковыми константами . У констант нет нескольких экземпляров. Это тот же адрес памяти. И адрес памяти имеет определенное значение после разыменования. Для символов имя указателя - это символ, а разыменованное значение - это строка, которая соответствует имени символа, в данном случае "ruby".

Находясь в хэше, вы используете не символ, указатель, а определяемое значение. Вы не используете :ruby, но "ruby". Затем хеш ищет ключ "ruby", значение равно "red"или "programming", в зависимости от того, как вы определили хеш.

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

Ahnbizcad
источник
В чем заключается заблуждение или ошибка в этом объяснении, противники? любопытно ради обучения.
ahnbizcad
просто потому, что аналогия может показаться некоторым неприятной, не означает, что она ошибочна.
ahnbizcad
2
Я не вижу ошибок, обязательно, но очень сложно определить, что вы пытаетесь сказать в этом ответе.
Обратный инжиниринг
x интерпретируется и концептуализируется по-разному в зависимости от контекста / объекта, выполняющего интерпретацию. довольно просто.
ahnbizcad
переработал ответ. Спасибо за ответ.
ahnbizcad