# ANN: EnumX.jl -- improved enums for Julia

**URL:** <https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096>\
**Category:** Package Announcements\
**Tags:** enum\
**Created:** [March 18, 2022, 4:56pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096 "2022-03-18T16:56:41Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [March 18, 2022, 4:56pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/1 "2022-03-18T16:56:41Z")

</div>

I wrote a small package [`EnumX`](https://github.com/fredrikekre/EnumX.jl) that I want to introduce.

Julia has builtin support for enums with the [`@enum`](https://docs.julialang.org/en/v1/base/base/#Base.Enums.@enum) macro, but such enums have some drawbacks, where the namespace/scope of the instances is the most annoying. This, and other problems with `@enum` have been discussed a lot on this forum (for example [here](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785), [here](https://discourse.julialang.org/t/solving-the-drawbacks-of-enum/74506), and [here](https://discourse.julialang.org/t/cannot-reuse-enum-member-in-different-enum/21342)). I decided to package some ideas from those threads into `EnumX.jl`, to make enums nicer to work with.

* * *

`EnumX` provides the `@enumx` macro, which, on the surface, is similar to `@enum` and have the same surface syntax, but with some nice improvements. Let’s look at an example: This defines an enum `Fruit` with the two instances `Apple` and `Banana`:

```julia
julia> using EnumX

julia> @enumx Fruit Apple Banana

```

The instances are accessed using dot-syntax:

```julia
julia> Fruit.Apple
Fruit.Apple = 0

julia> Fruit.Banana
Fruit.Banana = 1

```

The secret sauce here is that `Fruit` is a module, and the enum-type is instead defined as `Fruit.T` (this name can be configured, see the [README](https://github.com/fredrikekre/EnumX.jl/blob/master/README.md)):

```shell
julia> Fruit.T
Enum type Fruit.T <: Enum{Int32} with 2 instances:
 Fruit.Apple = 0
 Fruit.Banana = 1

```

This solves the problem with scope – the only name that is occupied by this enum is the module name `Fruit`, which means that it is possible to use “common names” for instances without polluting the namespace. It also means that it is possible to define another enum in the same namespace, with the same instance name(s), e.g.:

```julia
julia> @enumx AnotherFruit Apple Banana

```

* * *

`@enumx` has some other niceties such as:

- Support for duplicate values, e.g. `@enumx Fruit Apple = 1 Banana = 1`)
- Reusing previous instances for value initialization, e.g. `@enumx Fruit Apple = 3 Banana = Apple`)
- Support for docstrings:

```julia
"""
Documentation for Fruit enum.
"""
@enumx Fruit begin
    "Documentation for Fruit.Apple instance."
    Apple
    "Documentation for Fruit.Banana instance."
    Banana
end

```

* * *

Other than that, `@enumx` should be a drop-in replacement for `@enum`, and should support the same syntax and the same features, see the [README](https://github.com/fredrikekre/EnumX.jl/blob/master/README.md) for more details.

---

<div class="post-metadata">

**Author:** ![ultrapoci](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ultrapoci/32/25356_2.png) [@ultrapoci](https://discourse.julialang.org/u/ultrapoci)\
**Post date:** [March 22, 2022, 11:43am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/2 "2022-03-22T11:43:04Z")

</div>

This is really cool. Coming from Rust, I’ve always wondered why `enum`s aren’t first class citizen in so many languages. In Julia it happens often to have some kind of “mode” with which operate things, and having a small set of possible values to set said mode is just very handy.

---

<div class="post-metadata">

**Author:** ![pablosanjose](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pablosanjose/32/7006_2.png) [@pablosanjose](https://discourse.julialang.org/u/pablosanjose)\
**Post date:** [March 22, 2022, 12:03pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/3 "2022-03-22T12:03:30Z")

</div>

Beautiful, thank you!

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [March 23, 2022, 10:12am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/4 "2022-03-23T10:12:49Z")

</div>

> [@fredrikekre](#):
>
> `julia> @enumx Fruit Apple Banana`

This is really nice. I appreciate that the syntax mimics that of `@enum`, but always found it a bit strange that the name of the enumeration (in this case `Fruit`) is just part of the list of names.

It would look clearer if it said

```julia
@enumx Fruit: Apple Banana
# or
@enumx Fruit(Apple, Banana)

```

for example. Would that be possible to use as an optional syntax?

---

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [March 23, 2022, 10:19am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/5 "2022-03-23T10:19:14Z")

</div>

Both `@enum` and `@enumx` support block syntax, so you can use

```julia
@enumx Fruit begin
    Apple
    Banana
end

```

which clearly separates the name and the instances. Alternatively, adding the type annotation for the name, which also looks a bit like a separator (like `:` in your example)

```julia
@enumx Fruit::Int32 Apple Banana

```

It’s not difficult to support the other syntaxes though…

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [March 23, 2022, 7:34pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/6 "2022-03-23T19:34:30Z")

</div>

nicely done. thank you.

---

<div class="post-metadata">

**Author:** ![FireCrumb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/firecrumb/32/35370_2.png) [@FireCrumb](https://discourse.julialang.org/u/FireCrumb)\
**Post date:** [April 30, 2022, 8:40am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/7 "2022-04-30T08:40:07Z")

</div>

Hi @fredrikekre, there seems to be an overhead when using [@match](http://kmsquire.github.io/Match.jl/) on `@enumx` vs `@enum`, can you please take a look?

```julia
using EnumX
using Match

@enum FruitEnum begin
    Apple = 1
    Banana=2
end

@enumx FruitX begin
    Apple = 1
    Banana=2
end

function EatEnum(food::FruitEnum)
    @match food begin
        Apple => "very tasty"
        Banana => "maybe not"
    end
end

function EatEnumX(food::FruitX.T)
    @match food begin
        FruitX.Apple => "very tasty"
        FruitX.Banana => "maybe not"
    end
end

```

But matching on them generates vastly different code:

```julia
julia> @code_native EatEnum(Apple)
        .text
; ┌ @ REPL[14]:1 within `EatEnum`
        movabsq $140133313093552, %rax # imm = 0x7F7354591FB0
; │ @ REPL[14]:3 within `EatEnum`
        retq
        nopl (%rax,%rax)
; └

julia> @code_native EatEnumX(FruitX.Apple)
        .text
; ┌ @ REPL[15]:3 within `EatEnumX`
        cmpl $2, %edi
        movabsq $140131860384528, %rax # imm = 0x7F72FDC28B10
        movabsq $140133346115592, %rcx # imm = 0x7F7356510008
        cmoveq %rax, %rcx
; │┌ @ matchutils.jl:12 within `ismatch`
; ││┌ @ Base.jl:119 within `==`
        cmpl $1, %edi
        movabsq $140131860384400, %rax # imm = 0x7F72FDC28A90
; │└└
        cmovneq %rcx, %rax
        retq
        nopl (%rax)
; └

```

---

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [May 1, 2022, 12:09am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/8 "2022-05-01T00:09:44Z")

</div>

Seems to be a bug or wrong usage of `@match` – your `EatEnum` function returns `"very tasty"` for any input:

```julia
julia> @macroexpand @match food begin
           Apple => "very tasty"
           Banana => "maybe not"
       end
quote
    "very tasty"
end

```

```julia
julia> @macroexpand @match food begin
           FruitX.Apple => "very tasty"
           FruitX.Banana => "maybe not"
       end
quote
    if Match.ismatch(FruitX.Apple, food)
        "very tasty"
    else
        begin
            if Match.ismatch(FruitX.Banana, food)
                "maybe not"
            else
                nothing
            end
        end
    end
end

```

Edit: See [Match.jl#56](https://github.com/kmsquire/Match.jl/issues/56), [Match.jl#72](https://github.com/kmsquire/Match.jl/issues/72).

---

<div class="post-metadata">

**Author:** ![FireCrumb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/firecrumb/32/35370_2.png) [@FireCrumb](https://discourse.julialang.org/u/FireCrumb)\
**Post date:** [May 4, 2022, 1:23pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/9 "2022-05-04T13:23:30Z")

</div>

> [@fredrikekre](#):
>
> Seems to be a bug or wrong usage of `@match` – your `EatEnum` function returns `"very tasty"` for any input

Thanks for pointing that out, how silly of me not to do the basic testing and assuming it works. (But it seems I wasn’t alone in this, per comments in the issue to Match.jl)

So the following indeed works:

```julia
using EnumX
using Match

@enumx FruitX begin
    Apple = 1
    Banana = 2
end

function EatEnumX(food::FruitX.T)
    @match food begin
        FruitX.Apple => "very tasty"
        FruitX.Banana => "maybe not"
    end
end

@assert EatEnumX(FruitX.Apple) == "very tasty"
@assert EatEnumX(FruitX.Banana) == "maybe not"

@macroexpand @match food begin
    FruitX.Apple => "very tasty"
    FruitX.Banana => "maybe not"
end

```

What about [MLStyle.jl](https://thautwarm.github.io/MLStyle.jl/latest/syntax/pattern.html#support-pattern-matching-for-julia-enums) (mentioned in the issues) with EnumX?

```julia
using EnumX
using MLStyle
using MLStyle.AbstractPatterns: literal

@enumx FruitX begin
    Apple = 1
    Banana = 2
end

MLStyle.is_enum(::FruitX.T) = true
MLStyle.pattern_uncall(e::FruitX.T, _, _, _, _) = literal(e)

function EatEnumX(food::FruitX.T)
    @match food begin
        FruitX.Apple => "very tasty"
        FruitX.Banana => "maybe not"
    end
end

```

doesn’t work, getting this error:

```julia
LoadError: PatternCompilationError(:(#= In[1]:15 =#), ErrorException("unknown pattern syntax :(FruitX.Apple)"))

```

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [May 4, 2022, 9:51pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/10 "2022-05-04T21:51:43Z")

</div>

Isn’t it supposed to be `&FruitX.Apple`? Though maybe the raw form should work too, since [it works on Julia enums](https://thautwarm.github.io/MLStyle.jl/latest/syntax/pattern.html#support-pattern-matching-for-julia-enums).

---

<div class="post-metadata">

**Author:** ![FireCrumb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/firecrumb/32/35370_2.png) [@FireCrumb](https://discourse.julialang.org/u/FireCrumb)\
**Post date:** [May 5, 2022, 4:56am UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/11 "2022-05-05T04:56:48Z")

</div>

> [@jar1](#):
>
> Isn’t it supposed to be `&FruitX.Apple` ? Though maybe the raw form should work too, since [it works on Julia enums](https://thautwarm.github.io/MLStyle.jl/latest/syntax/pattern.html#support-pattern-matching-for-julia-enums).

You’re right, that indeed works, thanks!! 🤩

In fact, it then works even without `is_enum` and `pattern_uncall`, and furthermore it translates to LLVM’s `switch` statement!

```julia
using EnumX
using MLStyle

@enumx FruitX begin
    Apple = 1
    Banana = 2
end

function EatEnumX(food::FruitX.T)
    @match food begin
        &FruitX.Apple => "very tasty"
        &FruitX.Banana => "maybe not"
    end
end

```

---

<div class="post-metadata">

**Author:** ![freeman](https://avatars.discourse-cdn.com/v4/letter/f/ec9cab/32.png) [@freeman](https://discourse.julialang.org/u/freeman)\
**Post date:** [November 5, 2023, 12:50pm UTC](https://discourse.julialang.org/t/ann-enumx-jl-improved-enums-for-julia/78096/12 "2023-11-05T12:50:55Z")

</div>

What’s the reason why the EnumX doesn’t define `Base.convert(::Type{baseT}, x::MyEnum.T) = baseT(x)`?

I came across this when trying to define `ArrowTypes` hooks for my EnumX type. To my surprise, this isn’t enough:

```julia
ArrowTypes.ArrowType(::Type{MyEnumX.T}) = Base.Enums.basetype(MyEnumX.T)
ArrowTypes.arrowname(::Type{MyEnumX.T}) = Symbol("JuliaLang.MyEnumX")
ArrowTypes.JuliaType(::Val{Symbol("JuliaLang.MyEnumX")}) = MyEnumX.T

```

The reason for this is that the default way that ArrowTypes constructs types is by calling `convert`. I think this a sensible default as it’s the one most likely to work. For serializing into ArrowTypes I _can_ make it work by spelling out the way to do it:

```julia
ArrowTypes.toarrow(x::MyEnumX.T) = Base.Enums.basetype(MyEnumX.T)(x)

```

But it appears to me that if EnumX has a natural conversion into baseT (normally Int32), that probably should come defined in the package:

```julia
Base.convert(::Type{baseT}, x::MyEnum.T) = baseT(x)

```

Thoughts?
