Different ways to declare Enum datatype in Julia-Lang

11,894

Solution 1

It's the only (easy) way, yes. The answer, as often (or, rather, always) in Julia, can be found by looking at the source code. This can be a bit scary at first, but you get used to it after a while!

Normally to create an object of a given type, you call the type's constructor. So you might expect to be able to do

Enum(...)

and create an object of type Enum.

In this case, however, Enum is an abstract type, so you cannot do that.

What does @enum do, then? The example from the manual is

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

This actually creates a completely new type, called FRUIT, that is a subtype of Enum, and objects of that type called apple, orange and kiwi, which are converted to those numbers by calling Int(apple) etc. This is done by generating the Julia code to do so, inside the macro.

In principle, you could, yourself, do all the work that the macro does, but the macro is there to make our life easier!

Edit: Since Julia 0.7, enums can be defined using @enum macros as you mentionned but can also be used with a begin block:

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"

Enums can also be defined with this begin block without specifying values (in such a case values are starting with 0, not 1)

julia> @enum Fruit begin
           apple
           orange
           kiwi
       end

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

Solution 2

...and then there is the type abusing way of doing it; that I stumbled upon while thinking of types as names for sets:

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)

Note that the use of symbols is just a pretty bit of reflection, and is completely superfluous. You can put anything in there, and then recover it through type inspection

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

I am pretty sure the documentation explicitly recommends against this. Nevertheless @code_warntype does not particularly balk at this construct.

In set theoretic terms, each day alias is a singleton type, and thus a name for a set of exactly one element. The "Union" of "Type"s is then the set theoretic union of single element sets, forming the enumerated finite set type.

...and yet more type mangling ways of doing enumeration

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

Again @code_warntype does not seem to mind this at all. Finally one last technique that also provides a protected namespace for the enumeration

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"

In this last example to access the convenience property that gives an instance of a specific fruit we had to first instantiate the general fruits type. In the parlance of object oriented design Julia has no sense of static properties of types. The properties of types are only available once an explicit instance of that type has been constructed. The thought being that anything that is statically available about a particular type should be represented in some form of method overloading.

Solution 3

You can use SuperEnum (author here) to do some cool stuff:

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"
Share:
11,894

Related videos on Youtube

Reza Afzalan
Author by

Reza Afzalan

My studies is in Mechanical Engineering. I'm instructor of a mechanical laboratory in the south of Iran. My latest project is to develop a process simulation environment for trainees of petrochemical plants mostly in Julia-Lang and Java. love to program, do numerical engineering calculations. interested in Julia language, javafx and ...

Updated on June 30, 2020

Comments

  • Reza Afzalan
    Reza Afzalan almost 4 years

    Is using @enum the only way to declare Julia Enum datatype? If so why?

Related