I’m trying to define a type alias in a way that is actually restrictive. For instance, distance travelled = rate * time, where all 3 (distances, rates, and times) are inherently floats. I want to define 3 types: a “distance,” a “rate,” and a “time,” each of which is “really” just a float. However, I want to create a function (for instance, “distance_travelled”) that must take exactly one rate, and then exactly one time. Now this particular example might be doable with primitive types, except that I actually want vectors of floats, not just floats, for each of the three types.

For now, my workaround is to define three structs, each of which has just a single component - a vector of floats. But I’m concerned about possible performance losses from retrieving values from those structs, as well as just lack of elegance.

Perhaps I’m missing something quite straightforward - I apologize if that’s the case.

Don’t be. There is little to no overhead in accessing a field from a struct, as long as that field has a concrete type in the struct definition. Julia is less like Python (in which even looking up a single field involves accessing a dictionary) and more like C (in which structs exist only to help the programmer and have little to no runtime cost) in this regard.

Had the same problem, for dimensionless quantities (relative dielectric function: epsilon, refractive index: n).
Unitful.jl seems overkill here (and does not work under 0.7 yet), since these quantities are basically complex numbers.

A single field struct is ugly.

It does not seem possible to subtype from a concrete type, as explained here.

So let’s define a primitive type such as primitive type Float_n <: AbstractFloat 64 end
using reinterpret as explained in https://stackoverflow.com/a/49582867/3565696
allows back and forth conversions between Float64 and the custom Float_n.

# base Float type for refractive index n
primitive type Float_n <: AbstractFloat 64 end
Float_n(x::Float64) = reinterpret(Float_n, x)
# back to Float64
Float64(x::Float_n) = reinterpret(Float64, x)
# allow the REPL to show values of type Float_n
Base.show(io::IO, x::Float_n) = print(io, Float64(x))
# overload operators
Base.:+(x::Float_n, y::Float_n) = Float_n(Float64(x) + Float64(y))
Base.:-(x::Float_n, y::Float_n) = Float_n(Float64(x) - Float64(y))
Base.:*(x::Float_n, y::Float_n) = Float_n(Float64(x) * Float64(y))
Base.:/(x::Float_n, y::Float_n) = Float_n(Float64(x) / Float64(y))
# In my application it makes sense for mixed arguments operations to yield Float64
# it is always possible to explicitly reinterpret the result back to Float_n
Base.promote_rule(::Type{Float_n}, ::Type{Float64}) = Float64
julia> Float_n(1.0)
1.0
julia> typeof(ans)
Float_n

I’ve known it was possible to define ones own primitive types but I’ve never seen it done before.
Nice.

I’m not sure that doing so is a good idea, but ~shrug~.

I feel like there has been a discussion of such a restrictive type alias before.
I think it would be useful.

The properties one wants really are that
for XX being a restrictive type alias of X,
then XX is the closest subtype of X, and that X can be a concrete type.
So foo(::XX) would be considered more specific than foo(::X) for dispatch purposes.
You could even reasonably chain them I guess, making XXX a restrictive type alias of XX.

I am assuming that it will be fixed eventually for v0.7. I am not sure in what sense you consider it “overkill”, I think it is a convenient solution with nearly zero additional runtime cost (except for a bit of compilation of course).

Also, unless I want to twiddle bits, I would just define a wrapper type.

After reading twice the documentation, it looks like a complex package,
and I am still unsure how to use it for these dimensionless quantities.
I’ll try again when it is 0.7-ready.

What is a “wrapper type” ? How would you do that ?