@auramo, хороший вопрос и отличный выбор для лучшего ответа. Любите это или ненавидите, вы не получаете безопасность типов и (по крайней мере, в Ruby) защиту от опечаток. Я был в восторге, когда обнаружил перечисления в C # и позже в Java (выберите значение, но из них!), Ruby вообще не предоставляет реального способа сделать это в любом случае.
Дэн Розенстарк
2
Проблема с этим вопросом в том, что перечисления Java и C # - это совершенно разные вещи. Член перечисления Java - это экземпляр объекта и одиночка. Перечисление Java может иметь конструктор. Напротив, перечисления C # основаны на примитивных значениях. Какое поведение ищет спрашивающий? Хотя вполне вероятно, что дело C # является желательным, Java явно упоминается, а не C или C ++, поэтому есть некоторые сомнения. Что касается предположения, что в Ruby нет способа быть «безопасным», то это явно неверно, но вам нужно реализовать что-то более сложное.
user1164178
Ответы:
319
Два пути. Символы ( :fooобозначения) или константы ( FOOобозначения).
Символы подходят, когда вы хотите улучшить читаемость, не засоряя код буквальными строками.
Константы подходят, когда у вас есть базовое значение, которое важно. Просто объявите модуль для хранения ваших констант, а затем объявите константы внутри этого.
moduleFoo
BAR =1
BAZ =2
BIZ =4end
flags =Foo::BAR |Foo::BAZ # flags = 3
Что, если эти перечисления слишком сохранены в базе данных? Будет ли работать обозначение символа? Я сомневаюсь ...
Phương Nguyễn
Я бы использовал подход констант, если бы я сохранял в базу данных. Конечно, тогда вы должны выполнить какой-то поиск при извлечении данных из БД. Вы также можете использовать что-то вроде :minnesota.to_sпри сохранении в базе данных, чтобы сохранить строковую версию символа. Rails, я считаю, имеет несколько вспомогательных методов для решения некоторых из этих проблем.
mlibby
7
Разве модуль не будет лучше группировать константы - поскольку вы не собираетесь делать его экземпляры?
Thomthom
3
Просто комментарий. Руби немного беспокоит соглашения об именах, но они не совсем очевидны, пока вы не разберетесь с ними. Имена перечислений должны быть заглавными, а первая буква имени модуля должна быть написана заглавными буквами, чтобы ruby знал, что модуль является модулем констант.
Rokujolady
3
Не совсем верно. Первая буква константы должна быть заглавной, но не все буквы должны быть. Это вопрос общепринятых предпочтений. Например, все имена модулей и классов также являются константами.
Майкл Браун
59
Я удивлен, что никто не предложил что-то вроде следующего (полученного из RAPI gem):
classEnum
private
defself.enum_attr(name, num)
name = name.to_s
define_method(name +'?')do@attrs& num !=0end
define_method(name +'=')do|set|if set
@attrs|= num
else@attrs&=~num
endendend
public
def initialize(attrs =0)@attrs= attrs
enddef to_i
@attrsendend
Это хорошо работает в сценариях базы данных или при работе с константами / перечислениями стиля C (как в случае использования FFI , который RAPI широко использует).
Кроме того, вам не нужно беспокоиться о том, что опечатки могут вызывать сбои в режиме без вывода сообщений, как если бы вы использовали решение типа hash.
Это отличный способ решить эту конкретную проблему, но причина, по которой никто не предположил, что это, вероятно, связана с тем фактом, что он не очень похож на перечисления C # / Java.
mlibby
1
Это немного неполно, но служит хорошим намеком на то, как вы можете реализовать решения с помощью динамического подхода. Он имеет некоторое сходство с перечислением C # с набором FlagsAttribute, но, как и приведенные выше решения на основе символов / констант, это один из многих ответов. Проблема в исходном вопросе, который запутан в своем намерении (C # и Java не являются взаимозаменяемыми). В Ruby есть много способов перечислить объекты; Правильный выбор зависит от решаемой проблемы. Рабское копирование функций, которые вам не нужны, ошибочно. Правильный ответ должен зависеть от контекста.
user1164178
52
Самый идиоматический способ сделать это - использовать символы. Например, вместо:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... вы можете просто использовать символы:
# You don't actually need to declare these, of course--this is# just to show you what symbols look like.:foo
:bar
:baz
my_func(:foo)
Это немного более открытый, чем перечисления, но он хорошо сочетается с духом Ruby.
Символы также работают очень хорошо. Например, сравнение двух символов на равенство намного быстрее, чем сравнение двух строк.
Популярные платформы Ruby в значительной степени зависят от метапрограммирования во время выполнения, а выполнение слишком большой проверки времени загрузки отнимает большую часть выразительной силы Ruby. Чтобы избежать проблем, большинство программистов на Ruby используют тестовый дизайн, который находит не только опечатки, но и логические ошибки.
ЭМК
10
@yar: Ну, языковой дизайн - это серия компромиссов, и языковые особенности взаимодействуют. Если вам нужен хороший, высокодинамичный язык, используйте Ruby, сначала напишите свои модульные тесты и следуйте духу языка. :-) Если это не то, что вы ищете, есть десятки других отличных языков, каждый из которых имеет свой компромисс.
Emk
10
@emk, я согласен, но моя личная проблема в том, что я чувствую себя вполне комфортно в Ruby, но я не чувствую себя комфортно в рефакторинге в Ruby. И теперь, когда я начал писать модульные тесты (наконец-то), я понял, что они не панацея: я думаю, 1) что код Ruby не подвергается массовому рефакторингу, что часто, на практике, и 2) Ruby - не конец - с точки зрения динамических языков, именно потому, что трудно автоматически выполнить рефакторинг. См. Мой вопрос 2317579, который, как ни странно, был задан людьми из Smalltalk.
Дэн Розенстарк
4
Да, но использование этих строк не было бы в духе языка C #, это просто плохая практика.
Я думаю, что это лучший ответ. Функциональность, синтаксис и минимальные накладные расходы кода наиболее близки к Java / C #. Также вы можете вложить определения даже глубже одного уровня и при этом восстановить все значения с помощью MyClass :: MY_ENUM.flatten. В качестве примечания я бы использовал здесь имена в верхнем регистре, как стандарт для констант в Ruby. MyClass :: MyEnum может быть ошибочно принят за ссылку на подкласс.
Janosch
@ Janosch, я обновил имена. спасибо за предложение
Алексей
Я все еще немного растерялся, и ссылка 410'd (нет, не 404). Не могли бы вы привести примеры того, как будет использоваться этот enum?
Шелваку
17
Если вы используете Rails 4.2 или выше, вы можете использовать перечисления Rails.
Rails теперь имеет по умолчанию перечисления без необходимости включать какие-либо драгоценные камни.
Это очень похоже (и больше с функциями) на перечисления Java, C ++.
Как вы сказали - бесполезно, если OP не использует Rails (или, точнее, объект не относится к типу ActiveRecord). Просто объяснение моего отрицательного голоса - это все.
Ger
2
Это не перечисления в Ruby, это интерфейс ActiveRecord для перечислений в вашей базе данных. Не обобщаемое решение, которое может быть применено в любом другом случае использования.
Адам Лассек
Я уже упоминал об этом в своем ответе.
Vedant
Это лучший ответ IFF с использованием Rails.
Второе
Мне это не нравится, потому что он должен храниться в базе данных Rails (для работы) и потому что он позволяет создавать много экземпляров Conversationкласса - я считаю, что он должен разрешать только 1 экземпляр.
программа
8
Это мой подход к перечислениям в Ruby. Я собирался кратко и сладко, не обязательно самый C-like. Есть предположения?
ИМХО это очень близко повторяет использования и назначения (типобезопасность) от Java, а также, как вопрос предпочтения, константы могут быть определены следующим образом:class ABC; end
Wik
8
Я знаю, что прошло уже много времени с тех пор, как парень отправил этот вопрос, но у меня был тот же вопрос, и этот пост не дал мне ответа. Мне нужен был простой способ увидеть, что представляет число, простое сравнение и, прежде всего, поддержка ActiveRecord для поиска с использованием столбца, представляющего перечисление.
Я ничего не нашел, поэтому сделал потрясающую реализацию под названием yinum, которая позволила все, что я искал. Сделал тонну спецификаций, так что я уверен, что это безопасно.
Если вы беспокоитесь об опечатках с символами, убедитесь, что ваш код вызывает исключение при доступе к значению с несуществующим ключом. Вы можете сделать это, используя fetchвместо []:
my_value = my_hash.fetch(:key)
или заставляя хеш вызывать исключение по умолчанию, если вы предоставляете несуществующий ключ:
my_hash =Hash.new do|hash, key|
raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"end
Если хеш уже существует, вы можете добавить поведение, вызывающее исключения:
my_hash =Hash[[[1,2]]]
my_hash.default_proc = proc do|hash, key|
raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"end
Как правило, вам не нужно беспокоиться о безопасности опечаток с константами. Если вы неправильно напишите имя константы, обычно возникает исключение.
Кажется, вы выступаете за эмуляцию перечислений с хешами , не говоря об этом явно. Это может быть хорошей идеей, чтобы отредактировать свой ответ, чтобы сказать так. (Я в настоящее время есть потребность в чем - то вроде перечислений в Ruby, и мой первый подход к ее решению является использование хешей: FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}Это определяет ключевые символы. missing, somethingИ т.д., а также делает их сопоставимыми с помощью соответствующих значений.)
Теему Лейсти
Я имею в виду, не говоря об этом в самом начале ответа.
Теему Лейсти,
4
Кто-то пошел дальше и написал рубиновый камень под названием Renum . Он утверждает, что получил наиболее близкое поведение Java / C #. Лично я все еще изучаю Ruby, и я был немного шокирован, когда мне хотелось, чтобы конкретный класс содержал статическое перечисление, возможно, хеш, который не совсем легко найти через Google.
Я никогда не нуждался в перечислении в Ruby. Символы и константы идиоматичны и решают те же проблемы, не так ли?
Чак
Вероятно, Чак; но поиск глагола в ruby не поможет вам так далеко. Он покажет вам результаты для лучшей попытки людей на прямой эквивалент. Что заставляет меня задуматься, может быть, есть что-то хорошее в объединении концепции.
Дламблин
@ Чак Символы и константы не обеспечивают, например, что значение должно быть одним из небольшого набора значений.
Дэвид Моулз
3
Все зависит от того, как вы используете Java или C # перечисления. То, как вы его используете, будет определять решение, которое вы выберете в Ruby.
Почему бы не использовать символы Set[:a, :b, :c]?
Дэн Розенстарк
2
Намного лучше использовать здесь символы, ИМО.
Коллин Грейвс
3
Недавно мы выпустили гем, который реализует Enums в Ruby . В моем посте вы найдете ответы на свои вопросы. Также я описал, почему наша реализация лучше существующих (на самом деле в Ruby есть много реализаций этой возможности, но в виде гемов).
# bar.rb
require 'ostruct'# not needed when using Rails# by patching Array you have a simple way of creating a ENUM-styleclassArraydef to_enum(base=0)OpenStruct.new(map.with_index(base).to_h)endendclassBar
MY_ENUM =OpenStruct.new(ONE:1, TWO:2, THREE:3)
MY_ENUM2 =%w[ONE TWO THREE].to_enum
def use_enum (value)case value
when MY_ENUM.ONE
puts "Hello, this is ENUM 1"when MY_ENUM.TWO
puts "Hello, this is ENUM 2"when MY_ENUM.THREE
puts "Hello, this is ENUM 3"else
puts "#{value} not found in ENUM"endendend# usage
foo =Bar.new
foo.use_enum 1
foo.use_enum 2
foo.use_enum 9# put this code in a file 'bar.rb', start IRB and type: load 'bar.rb'
Символы это рубиновый путь. Тем не менее, иногда нужно поговорить с каким-нибудь кодом на C или чем-то или с Java, которые предоставляют различные перечисления для разных вещей.
#server_roles.rbmoduleEnumLikedefEnumLike.server_role
server_Symb=[:SERVER_CLOUD,:SERVER_DESKTOP,:SERVER_WORKSTATION]
server_Enum=Hash.new
i=0
server_Symb.each{|e| server_Enum[e]=i; i +=1}return server_Symb,server_Enum
endend
Вы прописываете второе слово в переменных (например server_Symb) по определенной причине? Если нет особой причины, то идиоматично для переменных быть snake_case_with_all_lower_case, а для символов быть :lower_case.
Эндрю Гримм
1
@Андрей; этот пример был взят из реальной жизни, а в документации сетевого протокола использовался xxx_Yyy, поэтому код на нескольких языках использовал одну и ту же концепцию, чтобы можно было следовать изменениям спецификации.
Jonke
1
Код гольф: server_Symb.each_with_index { |e,i| server_Enum[e] = i}. Нет необходимости i = 0.
Эндрю Гримм
2
Я реализовал перечисления, как это
moduleEnumTypedefself.find_by_id id
if id.instance_of?String
id = id.to_i
end
values.each do|type|if id == type.id
return type
endendnilenddefself.values
[@ENUM_1,@ENUM_2]endclassEnum
attr_reader :id,:label
def initialize id, label
@id= id
@label= label
endend@ENUM_1=Enum.new(1,"first")@ENUM_2=Enum.new(2,"second")end
Я бы не советовал этот подход, потому что он основан на ручной настройке значений и обеспечении правильного оформления заказа :VAL. Было бы лучше начать с массива и построить хеш с помощью.map.with_index
DaveMongoose
1
Точный смысл - привязать себя к значению, которое диктуют третьи стороны. Речь идет не о расширяемости как таковой, а о необходимости иметь дело с посторонними ограничениями, которые влияют на вычислимость в рамках вашего процесса.
JJK
Честная оценка! В этом случае определенно имеет смысл указать значения, но я бы предпочел выполнить обратный поиск с ключом .keyили .invertвместо него :VAL( stackoverflow.com/a/10989394/2208016 )
DaveMongoose,
Да, это (назад на тебя) справедливая точка зрения. Мой рубин был не элегантен и громоздок. Будет использовать def keyилиinvert
JJK
1
Большинство людей используют символы (это :foo_barсинтаксис). Это своего рода уникальные непрозрачные значения. Символы не принадлежат ни к какому типу enum-стиля, так что они на самом деле не являются точным представлением enum-типа C, но это в значительной степени так же хорошо, как и получается.
Иногда все, что мне нужно, - это получить значение enum и идентифицировать его имя, подобное миру java.
moduleEnumdef get_value(str)
const_get(str)enddef get_name(sym)
sym.to_s.upcase
endendclassFruits
include Enum
APPLE ="Delicious"
MANGO ="Sweet"endFruits.get_value('APPLE')#'Delicious'Fruits.get_value('MANGO')# 'Sweet'Fruits.get_name(:apple)# 'APPLE'Fruits.get_name(:mango)# 'MANGO'
Для меня это служит цели enum и делает его очень расширяемым. Вы можете добавить больше методов в класс Enum, и альты получат их бесплатно во всех определенных перечислениях. например. get_all_names и тому подобное.
Другой подход заключается в использовании класса Ruby с хешем, содержащим имена и значения, как описано в следующем сообщении в блоге RubyFleebie . Это позволяет вам легко преобразовывать значения и константы (особенно если вы добавляете метод класса для поиска имени для данного значения).
Я думаю, что лучший способ реализовать перечисления, подобные типам, - это использовать символы, так как они ведут себя как целые числа (когда дело доходит до производительности, для сравнения используется object_id); вам не нужно беспокоиться об индексации, и они выглядят очень аккуратно в вашем коде xD
Другой способ подражать перечислению с последовательной обработкой равенства (бесстыдно принятый от Дейва Томаса). Позволяет открывать перечисления (как символы) и закрытые (предопределенные) перечисления.
Ответы:
Два пути. Символы (
:foo
обозначения) или константы (FOO
обозначения).Символы подходят, когда вы хотите улучшить читаемость, не засоряя код буквальными строками.
Константы подходят, когда у вас есть базовое значение, которое важно. Просто объявите модуль для хранения ваших констант, а затем объявите константы внутри этого.
источник
:minnesota.to_s
при сохранении в базе данных, чтобы сохранить строковую версию символа. Rails, я считаю, имеет несколько вспомогательных методов для решения некоторых из этих проблем.Я удивлен, что никто не предложил что-то вроде следующего (полученного из RAPI gem):
Который можно использовать так:
Пример:
Это хорошо работает в сценариях базы данных или при работе с константами / перечислениями стиля C (как в случае использования FFI , который RAPI широко использует).
Кроме того, вам не нужно беспокоиться о том, что опечатки могут вызывать сбои в режиме без вывода сообщений, как если бы вы использовали решение типа hash.
источник
Самый идиоматический способ сделать это - использовать символы. Например, вместо:
... вы можете просто использовать символы:
Это немного более открытый, чем перечисления, но он хорошо сочетается с духом Ruby.
Символы также работают очень хорошо. Например, сравнение двух символов на равенство намного быстрее, чем сравнение двух строк.
источник
Я использую следующий подход:
Мне нравятся следующие преимущества:
MY_ENUM
MY_VALUE_1
Символы могут быть лучше, потому что вам не нужно писать имя внешнего класса, если вы используете его в другом классе (
MyClass::MY_VALUE_1
)источник
Если вы используете Rails 4.2 или выше, вы можете использовать перечисления Rails.
Rails теперь имеет по умолчанию перечисления без необходимости включать какие-либо драгоценные камни.
Это очень похоже (и больше с функциями) на перечисления Java, C ++.
Цитируется по адресу http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html :
источник
Conversation
класса - я считаю, что он должен разрешать только 1 экземпляр.Это мой подход к перечислениям в Ruby. Я собирался кратко и сладко, не обязательно самый C-like. Есть предположения?
источник
Проверьте драгоценный камень ruby-enum, https://github.com/dblock/ruby-enum .
источник
Возможно, лучший легкий подход будет
Таким образом, значения имеют связанные имена, как в Java / C #:
Чтобы получить все значения, вы можете сделать
Если вы хотите порядковый номер перечисления, вы можете сделать
источник
class ABC; end
Я знаю, что прошло уже много времени с тех пор, как парень отправил этот вопрос, но у меня был тот же вопрос, и этот пост не дал мне ответа. Мне нужен был простой способ увидеть, что представляет число, простое сравнение и, прежде всего, поддержка ActiveRecord для поиска с использованием столбца, представляющего перечисление.
Я ничего не нашел, поэтому сделал потрясающую реализацию под названием yinum, которая позволила все, что я искал. Сделал тонну спецификаций, так что я уверен, что это безопасно.
Некоторые примеры функций:
источник
Если вы беспокоитесь об опечатках с символами, убедитесь, что ваш код вызывает исключение при доступе к значению с несуществующим ключом. Вы можете сделать это, используя
fetch
вместо[]
:или заставляя хеш вызывать исключение по умолчанию, если вы предоставляете несуществующий ключ:
Если хеш уже существует, вы можете добавить поведение, вызывающее исключения:
Как правило, вам не нужно беспокоиться о безопасности опечаток с константами. Если вы неправильно напишите имя константы, обычно возникает исключение.
источник
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Это определяет ключевые символы.missing
,something
И т.д., а также делает их сопоставимыми с помощью соответствующих значений.)Кто-то пошел дальше и написал рубиновый камень под названием Renum . Он утверждает, что получил наиболее близкое поведение Java / C #. Лично я все еще изучаю Ruby, и я был немного шокирован, когда мне хотелось, чтобы конкретный класс содержал статическое перечисление, возможно, хеш, который не совсем легко найти через Google.
источник
Все зависит от того, как вы используете Java или C # перечисления. То, как вы его используете, будет определять решение, которое вы выберете в Ruby.
Попробуйте нативный
Set
тип, например:источник
Set[:a, :b, :c]
?Недавно мы выпустили гем, который реализует Enums в Ruby . В моем посте вы найдете ответы на свои вопросы. Также я описал, почему наша реализация лучше существующих (на самом деле в Ruby есть много реализаций этой возможности, но в виде гемов).
источник
Другое решение использует OpenStruct. Это довольно прямо и чисто.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Пример:
источник
Символы это рубиновый путь. Тем не менее, иногда нужно поговорить с каким-нибудь кодом на C или чем-то или с Java, которые предоставляют различные перечисления для разных вещей.
Это может быть использовано следующим образом
Это, конечно, можно сделать абстрактным, и вы можете бросить наш собственный класс Enum
источник
server_Symb
) по определенной причине? Если нет особой причины, то идиоматично для переменных бытьsnake_case_with_all_lower_case
, а для символов быть:lower_case
.server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Нет необходимостиi = 0
.Я реализовал перечисления, как это
тогда его легко делать операции
...
...
источник
Это кажется немного излишним, но эту методологию я использовал несколько раз, особенно там, где я интегрируюсь с xml или чем-то подобным.
Это дает мне строгость ac # enum, и это связано с моделью.
источник
:VAL
. Было бы лучше начать с массива и построить хеш с помощью.map.with_index
.key
или.invert
вместо него:VAL
( stackoverflow.com/a/10989394/2208016 )key
илиinvert
Большинство людей используют символы (это
:foo_bar
синтаксис). Это своего рода уникальные непрозрачные значения. Символы не принадлежат ни к какому типу enum-стиля, так что они на самом деле не являются точным представлением enum-типа C, но это в значительной степени так же хорошо, как и получается.источник
Вывод:
1 - a
2 - b
3 - c
4 - d
источник
to_enum
дает вам enumera Tor , в то время какenum
в C # / Java смысл является enumera ТионВывод:
источник
Иногда все, что мне нужно, - это получить значение enum и идентифицировать его имя, подобное миру java.
Для меня это служит цели enum и делает его очень расширяемым. Вы можете добавить больше методов в класс Enum, и альты получат их бесплатно во всех определенных перечислениях. например. get_all_names и тому подобное.
источник
Другой подход заключается в использовании класса Ruby с хешем, содержащим имена и значения, как описано в следующем сообщении в блоге RubyFleebie . Это позволяет вам легко преобразовывать значения и константы (особенно если вы добавляете метод класса для поиска имени для данного значения).
источник
Я думаю, что лучший способ реализовать перечисления, подобные типам, - это использовать символы, так как они ведут себя как целые числа (когда дело доходит до производительности, для сравнения используется object_id); вам не нужно беспокоиться об индексации, и они выглядят очень аккуратно в вашем коде xD
источник
Другой способ подражать перечислению с последовательной обработкой равенства (бесстыдно принятый от Дейва Томаса). Позволяет открывать перечисления (как символы) и закрытые (предопределенные) перечисления.
источник
Попробуйте инум. https://github.com/alfa-jpn/inum
увидеть больше https://github.com/alfa-jpn/inum#usage
источник