# Encapsulating enum access via dot syntax

**URL:** <https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785>\
**Category:** General Usage\
**Tags:** enum\
**Created:** [June 19, 2018, 3:08pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785 "2018-06-19T15:08:31Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![fbruetting](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fbruetting/32/4463_2.png) [@fbruetting](https://discourse.julialang.org/u/fbruetting)\
**Post date:** [June 19, 2018, 3:08pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/1 "2018-06-19T15:08:31Z")

</div>

Wouldn’t it make sense to access enums via dot syntax, together with maybe/optionally not to pushing all enum members into the scope? I currently have two issues:

- I am not able to create different enums with the same member names (which could have different meanings per enum context).
- At exporting an enum, the members don’t get exported. So I either have to write `<Module>.<Enum member>` or export all enum members.
  - The module prefix doesn’t seem nice/logical to me as the context (enum name) gets lost, and the module name may not have anything logically in common with the enum member name.
  - Exporting all members trashes the workspace/scope and blocks variable names.

This could be solved via `<Enum>.<Member>` access syntax, so that I can just export the pure enum names, access all members without difficulties and still have a clean workspace. This would also be more similar to the C style usage of enums.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [June 19, 2018, 3:17pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/2 "2018-06-19T15:17:52Z")

</div>

The workaround I’ve seen for access like this is to stash them inside a tiny module:

```julia
baremodule Fruits
using Base: @enum
@enum Fruit Apple Pear Banana
end

julia> Fruits.#<tab><tab>
Apple Banana Fruit Pear

```

Now in 0.7, we also have getproperty overloading, so perhaps another solution will emerge. Since this is “just” a macro it’s possible to build upon the base implementation for the semantics you want.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [June 19, 2018, 3:23pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/3 "2018-06-19T15:23:27Z")

</div>

> [@mbauman](#):
>
> Now in 0.7, we also have getproperty overloading, so perhaps another solution will emerge.

It would be _really_ nice to be able to have `Fruit` be a type yet have `Fruit.Apple`, etc. work. What is unlikely to be possible (at least as far as I can tell) is to have that work _and_ have `using Fruit` cause `Apple`, etc. to be available as short names, nor would `import Fruit: Apple` work. This is because there’s no way (currently) for a non-module to masquerade as a module.

---

<div class="post-metadata">

**Author:** ![quinnj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/quinnj/32/11_2.png) [@quinnj](https://discourse.julialang.org/u/quinnj)\
**Post date:** [June 19, 2018, 3:33pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/4 "2018-06-19T15:33:51Z")

</div>

Here’s a macro I’ve thrown together and use in a few projects:

```julia
macro scopedenum(T, args...)
    defs = Expr(:block)
    append!(defs.args, collect(:(const $(x.args[1]) = Enum($(x.args[2]))) for x in args))
    names = Dict(x.args[2]=>String(x.args[1]) for x in args)
    str2val = Dict(String(x.args[1])=>x.args[2] for x in args)
    push!(defs.args, quote
        function name(e::Enum)
            nms = $names
            return nms[e.value]
        end
        Enum(str::String) = Enum($(str2val)[str])
        Base.show(io::IO, e::E) where {E <: Enum} = print(io, "$(Base.datatype_module(E)).$(name(e)) = $(e.value)")
    end)
    blk = esc(:(module $T; struct Enum{T}; value::T; end; Enum{T}(e::Enum{T}) where {T} = e; $defs; end))
    return Expr(:toplevel, blk)
end

```

The basic usage is

```julia
@scopedenum Fruit APPLE=1 PEAR=2 BANANA=3
Fruit.APPLE
Fruit.PEAR
# access enum value
Fruit.APPLE.value
# make an APPLE from string
Fruit.Enum("APPLE")
# restricting type signatures
f(x::Fruit.Enum) = # do stuff w/ x

```

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [June 19, 2018, 3:35pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/5 "2018-06-19T15:35:22Z")

</div>

Ah, interesting. So this puts the type inside of the module. I guess you can’t have a `@scopedenum` called `Enum` but that’s a pretty minor limitation 😄

---

<div class="post-metadata">

**Author:** ![fbruetting](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fbruetting/32/4463_2.png) [@fbruetting](https://discourse.julialang.org/u/fbruetting)\
**Post date:** [June 20, 2018, 4:38pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/6 "2018-06-20T16:38:01Z")

</div>

Thanks for your suggestions!

@mbauman  
I get an error “getindex not defined” and have to add that, too (v0.6.2). Downside is, I then have more extensive definitions and `<Module>.<Enum>.<Value>` (like `Technologies.Technology.gan`) as return value, which seems a bit superfluous.

@quinnj  
Although I have to add the numbers manually, that seems more practical. It behaves not the same as a regular enum, but I will give it a try and will see if there are potential downsides.

---

<div class="post-metadata">

**Author:** ![fbruetting](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fbruetting/32/4463_2.png) [@fbruetting](https://discourse.julialang.org/u/fbruetting)\
**Post date:** [August 9, 2018, 7:14pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/7 "2018-08-09T19:14:37Z")

</div>

> [@StefanKarpinski](#):
>
> It would be _really_ nice to be able to have `Fruit` be a type yet have `Fruit.Apple` , etc. work. What is unlikely to be possible (at least as far as I can tell) is to have that work _and_ have `using Fruit` cause `Apple` , etc. to be available as short names, nor would `import Fruit: Apple` work. This is because there’s no way (currently) for a non-module to masquerade as a module.

`using Fruit` should explicitly _never_ pull pure `Apple` into the workspace, as this is exactly what I want to avoid. Instead `Apple` then should always be not defined and instead solely be a parameter to the `Fruit`-dot-syntax (⇒ `Fruit.Apple`). So all sorts of fruits should always be available via dot syntax, with `import Fruit: Apple` not being needed (but maybe optional). This would not be backwards-compatible to current `Enum`s, of course. (I hope I haven’t misunderstood your text.)

For me, `Fruit` could also just be of type `Enum` (or `NewEnum`, etc.). That may prevent dispatch, but would be at least a good intermediate solution.

Also named tuples have that kind of dot syntax I’d like to have for Enums. In fact the implementation of named tuples behaves more like the Enums I’d like to have, than Julia’s real Enums do… 😕

---

<div class="post-metadata">

**Author:** ![fbruetting](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fbruetting/32/4463_2.png) [@fbruetting](https://discourse.julialang.org/u/fbruetting)\
**Post date:** [October 5, 2018, 3:44pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/8 "2018-10-05T15:44:43Z")

</div>

If someone has a problem getting the above code to work in Julia v1.0 – you have to replace `datatype_module` with `parentmodule`. 🙂

---

<div class="post-metadata">

**Author:** ![quinnj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/quinnj/32/11_2.png) [@quinnj](https://discourse.julialang.org/u/quinnj)\
**Post date:** [March 2, 2019, 10:23pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/9 "2019-03-02T22:23:35Z")

</div>

Note that I’ve since revamped my `@scopedenum` code to act a little more like regular enums:

```julia
module ScopedEnums

using JSON2

export @scopedenum

macro scopedenum(T, args...)
    blk = esc(:(
        module $(Symbol("$(T)Module"))
            using JSON2
            export $T
            struct $T
                value::Int64
            end
            const NAME2VALUE = $(Dict(String(x.args[1])=>Int64(x.args[2]) for x in args))
            $T(str::String) = $T(NAME2VALUE[str])
            const VALUE2NAME = $(Dict(Int64(x.args[2])=>String(x.args[1]) for x in args))
            Base.string(e::$T) = VALUE2NAME[e.value]
            Base.getproperty(::Type{$T}, sym::Symbol) = haskey(NAME2VALUE, String(sym)) ? $T(String(sym)) : getfield($T, sym)
            Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value))
            JSON2.read(io::IO, ::Type{$T}) = $T(JSON2.read(io, String))
            JSON2.write(io::IO, x::$T) = JSON2.write(io, string(x))
        end
    ))
    top = Expr(:toplevel, blk)
    push!(top.args, :(using .$(Symbol("$(T)Module"))))
    return top
end

end

```

---

<div class="post-metadata">

**Author:** ![chris-b1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chris-b1/32/14165_2.png) [@chris-b1](https://discourse.julialang.org/u/chris-b1)\
**Post date:** [April 30, 2020, 9:46pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/10 "2020-04-30T21:46:12Z")

</div>

For anyone finding this off a google search like I did, here’s a slightly updated of @quinnj very helpful snipped that adds

- overloaded `propertynames` so REPL tab completion works
- iteration of enum members
- JSON3 StructType mapping rather than JSON2

```julia
macro option(T, args...)
    blk = esc(:(
        module $(Symbol("$(T)Module"))
            using JSON3
            export $T
            struct $T
                value::Int64
            end
            const NAME2VALUE = $(Dict(String(x.args[1])=>Int64(x.args[2]) for x in args))
            $T(str::String) = $T(NAME2VALUE[str])
            const VALUE2NAME = $(Dict(Int64(x.args[2])=>String(x.args[1]) for x in args))
            Base.string(e::$T) = VALUE2NAME[e.value]
            Base.getproperty(::Type{$T}, sym::Symbol) = haskey(NAME2VALUE, String(sym)) ? $T(String(sym)) : getfield($T, sym)
            Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value))
            Base.propertynames(::Type{$T}) = $([x.args[1] for x in args])
            JSON3.StructType(::Type{$T}) = JSON3.StructTypes.StringType()

            function _itr(res)
                isnothing(res) && return res
                value, state = res
                return ($T(value), state)
            end
            Base.iterate(::Type{$T}) = _itr(iterate(keys(VALUE2NAME)))
            Base.iterate(::Type{$T}, state) = _itr(iterate(keys(VALUE2NAME), state))
        end
    ))
    top = Expr(:toplevel, blk)
    push!(top.args, :(using .$(Symbol("$(T)Module"))))
    return top
end

```

---

<div class="post-metadata">

**Author:** ![sapo](https://avatars.discourse-cdn.com/v4/letter/s/ec9cab/32.png) [@sapo](https://discourse.julialang.org/u/sapo)\
**Post date:** [May 30, 2021, 9:00am UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/11 "2021-05-30T09:00:22Z")

</div>

This version also allows for enums without explicit values:

```julia
macro myenum(T, args...)
    counter = 0
    function key_value(x)
        if hasproperty(x, :args)
            key = x.args[1]
            value = x.args[2]
        else
            key = x
            value = counter
            counter += 1
        end
        return key, value
    end
    _args = [key_value(x) for x in args]

    blk = esc(:(
        module $(Symbol("$(T)Module"))
            using JSON3
            export $T
            struct $T
                value::Int64
            end
            const NAME2VALUE = $(Dict(String(x[1])=>Int64(x[2]) for x in _args))
            $T(str::String) = $T(NAME2VALUE[str])
            const VALUE2NAME = $(Dict(Int64(x[2])=>String(x[1]) for x in _args))
            Base.string(e::$T) = VALUE2NAME[e.value]
            Base.getproperty(::Type{$T}, sym::Symbol) = haskey(NAME2VALUE, String(sym)) ? $T(String(sym)) : getfield($T, sym)
            Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value))
            Base.propertynames(::Type{$T}) = $([x[1] for x in _args])
            JSON3.StructType(::Type{$T}) = JSON3.StructTypes.StringType()

            function _itr(res)
                isnothing(res) && return res
                value, state = res
                return ($T(value), state)
            end
            Base.iterate(::Type{$T}) = _itr(iterate(keys(VALUE2NAME)))
            Base.iterate(::Type{$T}, state) = _itr(iterate(keys(VALUE2NAME), state))
        end
    ))
    top = Expr(:toplevel, blk)
    push!(top.args, :(using .$(Symbol("$(T)Module"))))
    return top
end

```

Example:

```julia
julia> @myenum Foo a b

julia> Foo.a
Foo.a = 0

julia> Foo.b
Foo.b = 1

julia> Foo(0)
Foo.a = 0

julia> Foo(1)
Foo.b = 1

```

---

<div class="post-metadata">

**Author:** ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)\
**Post date:** [May 31, 2021, 2:47pm UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/12 "2021-05-31T14:47:10Z")

</div>

Note that overloading getproperty on a Type is currently considered type piracy, and can break things in all sorts of interesting and wonderful (aka bad) ways. (though that might change [use `getfield` to access type fields in reflection by JeffBezanson · Pull Request #39573 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/39573))

---

<div class="post-metadata">

**Author:** ![sapo](https://avatars.discourse-cdn.com/v4/letter/s/ec9cab/32.png) [@sapo](https://discourse.julialang.org/u/sapo)\
**Post date:** [June 1, 2021, 7:26am UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/13 "2021-06-01T07:26:48Z")

</div>

The thing is that while interfacing with existing C code, having enums would be really nice. An equivalent solution would be to be able to subtype Dict. Is that possible?

---

<div class="post-metadata">

**Author:** ![astadmistry](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/astadmistry/32/17299_2.png) [@astadmistry](https://discourse.julialang.org/u/astadmistry)\
**Post date:** [August 31, 2021, 2:05am UTC](https://discourse.julialang.org/t/encapsulating-enum-access-via-dot-syntax/11785/14 "2021-08-31T02:05:01Z")

</div>

This version also allows for a begin end block:  
Thank you `@edit @enum` 😅

```julia
macro scopedenum(T, syms...)
    counter = 0 
    function key_value(x)
        if hasproperty(x, :args)
            k = x.args[1]
            v = x.args[2]
        else
            k = x
            v = counter
            counter += 1
        end
        return k,v
    end

    if length(syms) == 1 && syms[1] isa Expr && syms[1].head === :block
        syms = syms[1].args
    end

    syms = Tuple(x for x in syms if ~(x isa LineNumberNode))
    _syms = [key_value(x) for x in syms if ~(x isa LineNumberNode)]

    blk = esc(:(
        module $(Symbol("$(T)Module"))
            using JSON3
            export $T
            struct $T
                value::Int64
            end
            const NAME2VALUE = $(Dict(String(x[1])=>Int64(x[2]) for x in _syms))
            $T(str::String) = $T(NAME2VALUE[str])
            const VALUE2NAME = $(Dict(Int64(x[2])=>String(x[1]) for x in _syms))
            Base.string(e::$T) = VALUE2NAME[e.value]
            Base.getproperty(::Type{$T}, sym::Symbol) = haskey(NAME2VALUE, String(sym)) ? $T(String(sym)) : getfield($T, sym)
            Base.show(io::IO, e::$T) = print(io, string($T, ".", string(e), " = ", e.value))
            Base.propertynames(::Type{$T}) = $([x[1] for x in _syms])
            JSON3.StructType(::Type{$T}) = JSON3.StructTypes.StringType()

            function _itr(res)
                isnothing(res) && return res
                value, state = res
                return ($T(value), state)
            end
            Base.iterate(::Type{$T}) = _itr(iterate(keys(VALUE2NAME)))
            Base.iterate(::Type{$T}, state) = _itr(iterate(keys(VALUE2NAME), state))
        end
    ))
    top = Expr(:toplevel, blk)
    push!(top.args, :(using .$(Symbol("$(T)Module"))))
    return top
end

```

Example 1:

```julia
julia> @scopedenum TransportState begin
           DISCONNECTED=1
           SERVER_DISCONNECTED=2
           CONNECTING=3
           CONNECTED=4
           DISCONNECTING=5
       end

julia> TransportState.DISCONNECTED
TransportState.DISCONNECTED = 1

julia> TransportState.CONNECTING
TransportState.CONNECTING = 3

```

Example 2:

```julia
julia> @scopedenum TransportState begin
           DISCONNECTED
           SERVER_DISCONNECTED
           CONNECTING
           CONNECTED
           DISCONNECTING
       end

julia> TransportState.DISCONNECTED
TransportState.DISCONNECTED = 0

julia> TransportState.CONNECTING
TransportState.CONNECTING = 2

```

Example 3:

```julia
julia> @scopedenum TransportState DISCONNECTED=1 SERVER_DISCONNECTED=2 CONNECTING=3 CONNECTED=4 DISCONNECTING=5

julia> TransportState.DISCONNECTED
TransportState.DISCONNECTED = 1

julia> TransportState.CONNECTING
TransportState.CONNECTING = 3

```

Example 4:

```julia
julia> @scopedenum TransportState DISCONNECTED SERVER_DISCONNECTED CONNECTING CONNECTED DISCONNECTING

julia> TransportState.DISCONNECTED
TransportState.DISCONNECTED = 0

julia> TransportState.CONNECTING
TransportState.CONNECTING = 2

```
