Различные способы объявить тип данных Enum в Julia-Lang

Использует @enum единственный способ объявить тип данных Julia Enum? Если да, то почему?

Ответ 1

Это единственный (легкий) способ, да. Ответ, как часто (а точнее, всегда) у Юлии, можно найти, взглянув на исходный код. Поначалу это может быть немного страшно, но через некоторое время вы к этому привыкаете!

Обычно для создания объекта данного типа вы вызываете конструктор типа. Таким образом, вы можете ожидать, что сможете сделать

Enum(...)

и создайте объект типа Enum.

Однако в этом случае Enum является абстрактным типом, поэтому вы не можете этого сделать.

Что же @enum делает @enum? Пример из руководства

julia> @enum FRUIT apple=1 orange=2 kiwi=3

Это фактически создает совершенно новый тип, называемый FRUIT, который является подтипом Enum, и объекты этого типа, называемые apple, orange и kiwi, которые преобразуются в эти числа с помощью вызова Int(apple) и т.д. Это делается путем генерации Юлия код для этого внутри макроса.

В принципе, вы могли бы самостоятельно выполнить всю работу, которую выполняет макрос, но этот макрос призван сделать нашу жизнь проще!

Изменение: Начиная с Julia 0.7, перечисления могут быть определены с @enum макросов @enum как вы упоминали, но также могут использоваться с блоком начала:

julia> @enum Fruit begin
           apple = 1
           orange = 2
           kiwi = 3
       end

julia> Fruit
Enum Fruit:
apple = 1
orange = 2
kiwi = 3

julia> apple
apple::Fruit = 1

julia> orange
orange::Fruit = 2

julia> kiwi
kiwi::Fruit = 3

julia> Int(orange)
2

julia> string(orange)
"orange"

Перечисления также могут быть определены с помощью этого начального блока без указания значений (в этом случае значения начинаются с 0, а не с 1)

julia> @enum Fruit begin
           apple
           orange
           kiwi
       end

julia> Fruit
Enum Fruit:
apple = 0
orange = 1
kiwi = 2

Ответ 2

... и тогда существует тип злоупотребления этим способом; что я наткнулся, думая о типах как именах наборов:

typealias Sunday Val{:Sunday}
typealias Monday Val{:Monday}
typealias Tuesday Val{:Tuesday}
typealias Wednesday Val{:Wednesday}
typealias Thursday Val{:Thursday}
typealias Friday Val{:Friday}
typealias Saturday Val{:Saturday}

typealias Days Union{
    Type{Sunday}, 
    Type{Monday}, 
    Type{Tuesday}, 
    Type{Wednesday}, 
    Type{Thursday}, 
    Type{Friday}, 
    Type{Saturday}
}

function daynumber(d::Days)
    if d == Sunday return 0
    elseif d == Monday return 1
    elseif d == Tuesday return 2
    elseif d == Wednesday return 3
    elseif d == Thursday return 4
    elseif d == Friday return 5
    elseif d == Wednesday return 6
    end
    -1
end

> daynumber(Friday)
  5
> daynumber(:Friday)
  > MethodError:`daynumber` has no method matching (::Symbol)

Обратите внимание, что использование символов - всего лишь небольшая часть отражения, и совершенно излишне. Вы можете положить что-нибудь там, а затем восстановить его через проверку типа

> x = Saturday.parameters[1]
  :Saturday
> typeof(x)
  Symbol
> eval(x) == Saturday
  true

Я уверен, что документация явно рекомендует против этого. Тем не менее @code_warntype особо не перекрывает эту конструкцию.

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

... и еще больше типов манипулирования перечислением

abstract Fruits{N} <: Enum
immutable Apples <: Fruits{1} end
immutable Oranges <: Fruits{2} end
immutable Bananas <: Fruits{3} end

fruitsalad{N}(x::Fruits{N}) = N

> anorange = Oranges()
> fruitsalad(anorange)
  2

Снова @code_warntype, похоже, вообще ничего против. Наконец, последний метод, который также предоставляет защищенное пространство имен для перечисления

immutable Fruits{N} <: Enum
    apples::Fruits
    bananas::Fruits
    oranges::Fruits
    function Base.call(::Type{Fruits})
        new{"anything"}(
            Fruits{"crunchy"}(),
            Fruits{"mushy"}(),
            Fruits{"tangy"}()
        )
    end
    function Base.call{N}(::Type{Fruits{N}})
        if N != "crunchy" && N != "mushy" && N != "tangy"
            error("Invalid enumeration parameter")
        end
        new{N}()
    end
end

fruitsalad{N}(x::Fruits{N}) = N

> fruitsalad(Fruits().apples)
  "crunchy"

В этом последнем примере для доступа к удобному свойству, которое дает экземпляр конкретного плода, мы должны сначала создать экземпляр общего типа плодов. В языке объектно-ориентированного дизайна Юлия не имеет смысла статических свойств типов. Свойства типов доступны только после того, как был создан явный экземпляр этого типа. Мысль о том, что все, что статически доступно для определенного типа, должно быть представлено в некоторой форме перегрузки метода.

Ответ 3

Вы можете использовать SuperEnum (автор здесь), чтобы сделать некоторые интересные вещи:

using Pkg
Pkg.add("https://github.com/kindlychung/SuperEnum.jl")
using SuperEnum

@se Vehical plane train car truck

julia> Vehical.VehicalEnum
Enum Main.Vehical.VehicalEnum:
plane = 0
train = 1
car = 2
truck = 3

julia> Vehical.car
car::VehicalEnum = 2

julia> @se Lang zh=>"中文"*"Chinese" en=>"English" ja=>"日本语"
Main.Lang

julia> string(Lang.zh)
"中文Chinese"