Enums with multiple non Integer values

I am looking to reproduce enumerated value type such as available in Java and Kotlin. The values are singletons and play nicely as keys in dictionaries. A text book (Java) example looks like this:

    public enum Planet {
        MERCURY(3.303e+23, 2.4397e6),
        EARTH(5.976e+24, 6.37814e6),
        // ...
        final double mass;   // in kilograms
        final double radius; // in meters

        Planet(double mass, double radius) { this.mass = mass;this.radius = radius; }
    }
    
    public static void main(String[] args) {
        System.out.println(Planet.MERCURY); // MERCURY
        System.out.println(Planet.EARTH.radius); // 6378140.0
    }

What is a Julia idiomatic way to produce something similar?

How about

struct Planet
    mass::Float64
    radius=Float64
end
const MERCURY = Planet(3.303e+23, 2.4397e6)
...
1 Like

I suppose that for use in sets or dictionaries, I will need to implement isequal and hash. Also how would I restrict users from creating additional planets (say we are interested only in our solar system)?

You can use @auto_hash_equals to automatically generate hash and isequal for you.

Why would you go out of your way to forbid users from creating additional planets? If the code will have assumptions broken by arbitrary planets just document this information.

1 Like

Thanks for the macro tip. As to documenting, I’d rather have the type system do the work instead.

Well, the problem is: this solution has nothing to do with the type system (except by the fact you have a type named Planet). The MERCURY name is just the name of a global const binding inside your module. It is indistinguishable to the type system from any other Planets objects you may instance. What you can do is to keep a const global Set of Planets with the limited set of instances that you want to restrict the users to, and @assert the arguments of each function are in this Set.

A solution that would use the type system would be:

const MERCURY = Val(:Mercury)

mass(::Val{:Mercury}) = 3.303e+23
radius(::Val{:Mercury}) = 2.4397e6

This by itself does not prevent new planets being created (because the mass and radius function can be extended), but then you can create a type like:

const SolarSystemPlanet = Union{Val{:Mercury}, Val{:Venus}, #=every other planet here=#}

and use this type to restrict the parameters of your functions:

unwrap(::Val{T}) where {T} = T

function my_function(planet :: SolarSystemPlanet)
    println(unwrap(planet))
end

But this will have impacts on compilation latency (i.e., each function taking a SolarSystemPlanet will be specialized for each different planet it may receive). An way to avoid it is the @nospecialize macro.

1 Like

Feels like fighting the system too much, I’ll try to model problem in a different way.