Encapsulating enum access via dot syntax


#1

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.


#2

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

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.


#3

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.


#4

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

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

@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

#5

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 :smile:


#6

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.