Как определить имя переменной как строку самостоятельно?


Я написал оболочку для библиотеки Ruby OpenStruct. Используя эту оболочку, пользователь объявляет переменную примерно так:

example = RStruct.new()

Я хотел бы получить доступ к имени переменной следующим образом:

example.name # => "example"

Это может оказаться полезным при работе внутри класса и вызове self.name

Я построил класс RStruct, поэтому добавить метод name не составит труда. Я ищу переменную пользователя из self.

Если какой-то язык и может это сделать, то это Ruby, верно?
2 2

2 ответа:

Эта ситуация всплывает в Ruby так часто, что я написал для нее драгоценный камень. То, что вы обычно делаете в vanilla Ruby, будет выглядеть примерно так:

fred = Foobar.new( name: 'fred' )

Мало того, что "Фред" повторяется дважды в приведенной выше строке, но также вы должны угодить именованному параметру :fred, и вы должны делать рутинную работу, такую как написание метода RStruct#name и т. д. Если Вы gem install y_support, то можете написать:

require 'y_support/name_magic'

class Foobar
  include NameMagic
end

А теперь:

Fred = Foobar.new
Fred.name #=> "Fred"

Вы ограничены тем, что только присваиваете константы (начиная с a заглавная буква) работают, чтобы назвать безымянный объект, класс которого включает NameMagic, но в любом случае "Фред" должен быть заглавным. Константы Ruby удваиваются как заглавные имена собственных объектов, а модули Ruby иногда также называются пространствами имен. Это согласуется с поведением именования классов с помощью константного присваивания, такого как Animal = Class.new, также известного как Ruby built-in constant magic.


ПРИЛОЖЕНИЕ, БОЛЕЕ ПОДРОБНО ОПИСЫВАЮЩЕЕ ФУНКЦИИ МАГИИ ИМЕН:

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

Foobar.instances
#=> [Fred]

Сам реестр является хэшем и может быть доступен через:

Foobar.__instances__ # Don't modify this hash unless you know what you are doing.

Гораздо более полезным, чем кажется, является метод #instance:

Foobar.instance "Fred"
#=> Fred
Foobar.instance Fred
#=> Fred

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

Array распространяется с Метод #names, так что вы можете легко получить имена объектов в массиве (я подтвердил себе, что это часто необходимая функция):

Foobar.instances.names
#=> ["Fred"]

Hash расширяется с помощью метода #keys_to_names, поэтому вы можете написать { Fred => 42 }.keys_to_names, чтобы переключиться с использования объектов на использование их имен. Эта функция не так часто нужна, но я включил ее, поскольку именование тесно связано с хэшированием, имя-это своего рода хэш-функция, которая, как предполагается, имеет смысл и легко запоминается людьми. Так много для расширений к основные классы. Другие методы, которые вы можете посмотреть в документации, включают Foobar.nameless_instances, Foobar.forget, Foobar.forget_nameless_instances, Foobar.forget_all_instances (чтобы позволить забытым объектам быть GCed, это просто для расширенного использования и для того, чтобы развеять гипотетические жалобы на то, что драгоценный камень съедает память).

Текущая версия драгоценного камня сильно ломается, когда вы дважды назначаете константу, которая уже исправлена в следующей запланированной версии, выпуск которой сдерживается зависимостью (еще одна моя драгоценность). Кроме того, Спасибо, что указали мне к способу подавления раздражающих предупреждений, генерируемых при прикосновении к некоторым неясным константам в глубоком пространстве имен.

Gem позволяет также именовать более стандартным методом, используя параметр :name, а не постоянное назначение, так что это еще одна рутинная работа, которую пользователь gem не должен делать:

Foobar.new name: "Joe"
Foobar.instances #=> [Fred, Joe]

Или с помощью #name= сеттера.

x = Foobar.new
x.name = "Sam"
Foobar.instances #=> [Fred, Joe, Sam]
Последняя полезная функция, которую я хочу ввести здесь, - это два крючка, #name_set_hook и #name_get_hook. Первый, если он очень полезен для проверки и / или измените предлагаемое имя. Кроме того, есть хороший шанс, что вам нужно будет выполнить некоторый код не при создании экземпляра (потому что экземпляры, использующие name_magic, рождаются безымянными), а только тогда, когда имя известно, например, регистрируя объект под его именем в различных ваших коллекциях. #name_set_hook - это место, где можно поместить этот код. Эта функция немного ненадежна, в том смысле, что вы должны знать правила:
  1. предполагается, что блок будет тернарным, с упорядоченными аргументами name, экземпляр и предыдущее имя.
  2. Блок должен возвращать допустимое имя, которое будет использоваться вместо предложенного имени. Это дает возможность изменить имя, но если вы не хотите его изменять, вы все равно должны вернуть неизмененную версию.
  3. код блока должен воздерживаться от прямого или косвенного запуска запроса об имени до сих пор безымянного экземпляра, иначе последует бесконечный цикл. В частности, #inspect и #to_s метод не должен вызываться для безымянного пример. Делать это в любом другом месте нормально, но не в этом блоке.

Foobar.name_set_hook { |name, instance, old_name|
  puts "Name #{name} was requsted for object #{instance.object_id}." # not "#{instance}",
  # because "#{instance}" would invoke instance.to_s method.
  puts "Such name is not acceptable to our church."
  modified_name = "#{name}x"
  puts "The name will be #{modified_name}."
  # notify your tables that the object has just been named with modified_name
  acceptable_name # the block must return the name to be used, even if it doesn't modify it
}

Nathan = Foobar.new
#=> Name Nathan was requsted for object 76778240.
#=> Such name is not acceptable to our church.
#=> The name will be Nathanx.

Foobar.instances #=> [Fred, Joe, Sam, Nathanx]
Гораздо менее полезным крючком является

. Это полезно только тогда, когда вы хотите, чтобы имена имели более одного альтернативного способа представления, и вы хотите выбрать конкретный. Например, физические единицы могут содержаться в виде констант all-caps, таких как

class Unit; include NameMagic end
GRAM, METRE, SECOND = Unit.new, Unit.new, Unit.new
#=> [GRAM, METRE, SECOND]

Но вы можете захотеть, чтобы они представляли себя в нижнем регистре:

Unit.name_get_hook { |name| name.downcase }
Unit.instances
#=> [gram, metre, second]

Метод #instance распознает оба случая:

Unit.instance "metre"
#=> metre
Unit.instance "METRE"
#=> metre

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

Unit.__instances__[ Unit.instance( "metre" ) ]
#=> "METRE"
Это был мой фактический случай использования, который вдохновил меня добавить функцию #name_get_hook, которая необходима, потому что имена должны начинаться с заглавной буквы, но Соглашение для единиц состоит в том, чтобы писать их во всех маленьких буквах. Другими словами, скрытое правило использования #name_get_hook состоит в том, что должен существовать однозначный способ получения фактического имя из представленной альтернативной формы.

GitHub repo драгоценного камня - это https://github.com/boris-s/y_support а теперь я должен взять себя в руки и поместить этот текст в файл README lol.

Переменные не являются объектами. Вы ничего не можете с ними сделать, кроме как назначить им и разыменовать их.

example.name

Будет всегда означать "разыменование example и посылать сообщение name в результат разыменования example", это Никогда не будет означать "посылать сообщение name в переменную example", потому что а) это приведет к неоднозначности, и Б) переменные не являются объектами, поэтому вы не можете отправлять сообщения им.