Based on the discussion in the above thread I am refactoring my code to dispatch on instances instead of types (which also helps me to avoid some illegal stuff I was doing, e.g. overloading Base.show for types). But now I have a (silly) naming problem…
Let’s assume I have a singleton type representing the planet Mars:
struct Mars end
Now I have a function that returns its radius. In the past I would have written it like this
radius(::Type{Mars}) = 3389.5
which makes a nice looking call possible
radius(Mars)
If I want to use the instance instead this becomes
radius(::Mars) = 3389.5
radius(Mars()) # ugly
Not only do I think that the empty parens are ugly but also a potential source of errors for newcomers, e.g.
julia> radius(Mars)
ERROR: MethodError: no method matching radius(::Type{Mars})
Possible solutions:
Do it like Base
struct Mars end
const mars = Mars()
This is not really satisfying because Mars is a proper noun and should start with a capital letter.
Add a *Type suffix
# Ugly name for the type
struct MarsType end
# Nice name for the instance
const Mars = MarsType()
# Hide the ugly type name from the user
Base.show(io::IO, ::MarsType) = print(io, "Mars")
# Ugly code
radius(::MarsType) = 3389.5
# Nice call
radius(Mars)
Also not really satisfying because I have to type MarsType a lot and it feels like an implementation detail leaking out.
The Julia convention is have instance-bindings in lower-case, irrespective of proper English spelling, eg einstein = Physicist(name="Einstein") would be correct. I think it would be fine to do the const mars = Mars() and then only export mars.
Thanks! It seems the only problems here are my aesthetic sensibilities
How would you handle the following case?
# 1
struct UTC <: TimeScale end
const utc = UTC()
# 2
struct Utc <: TimeScale end
const UTC = Utc()
# 3
struct UTCScale <: TimeScale end
const UTC = UTCScale()
Base.show(io::IO, ::UTCScale) = print(io, "UTC")
This does not answer the question which naming convention is better, but personally this is my preferred way to organize such code.
Like this you can dissociate the instance name from the struct’s name, and avoids creating a lot of singleton types.
For the timescale I would do something similar.
Hope this helps.
Thanks for the suggestion, but Mars being a singleton is non-negotiable for my implementation. I need this to be able to dispatch on the type like in this simplified example:
# I have a state vector type that has the central body as a type parameter
struct State{T} end
function State{S}(s::State{T}) where {S<:Planet, T<:Planet}
# do the transformation
end
mars_state = State{Mars}(...)
# Magically transform the state from Mars-centered to Earth-centered
earth_state = State{Earth}(mars_state)
And I also like the fact that there cannot be competing instances of Mars this way
Until some time ago I’d have agreed with you and this is how planets are defined in AstroLib.jl. However, I see the point of Helgee’s approach: if you want to add a property to a planet you don’t have to change the data structure and planet instances, but just define a new method, perhaps not necessary for all planets.
@helgee: we should probably have an AstroBase.jl package where to move this stuff and avoid code/numbers duplication
Yeah, I totally agree this is a powerful way to extend behaviour and I wouldn’t know how to do this in general with the “struct Planet” approach.
On the other hand, if I want to add behaviour, I find it quite easy to add a field to the Planet struct and then write an outer constructor such that the old code still works. I think this was explained in the discussion linked by Helgee.