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.


#7

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 Enums, 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… :confused:


#8

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