Restrictive Type Aliases?


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.



Structs should work, and the performance overhead shouldn’t be too bad as immutable containers in most cases should be elided at compile time.

Or you could just use Unitful.jl:

julia> import Unitful: km, hr

julia> rate = 60km/hr
60 hr^-1 km

julia> time = 3.14hr
3.14 hr

julia> rate*time
188.4 km

Vectors of these should mostly just work as well.


function x(z::Array{Float64,1}) will restrict the input arg to a 1d array of floats
Otherwise the .() function variant could be used

function vdist(xrate::Float64, xtime::Float64)
  return xrate * xtime;

r1 = collect(1:1.3:10);
t1 = r1[end:-1:1];

d1 = vdist.(r1, t1);

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.

And if you’re really concerned about performance, is your friend :slightly_smiling_face:


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
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, 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)

julia> typeof(ans)

preliminary benchmarks look fine:

julia> using BenchmarkTools
julia> @btime Float_n(1.0) + Float_n(2.0)
  0.014 ns (0 allocations: 0 bytes)
julia> @btime 1.0 + 2.0
  0.014 ns (0 allocations: 0 bytes)
@btime Float_n(1.0) + 2.0
  0.014 ns (0 allocations: 0 bytes)

This is not perfect:

julia> a = rand(Float_n, 10000);
ERROR: ArgumentError: Sampler for this object is not defined

It would be nice for Float_n to work as Float64 when a method does not match.


You would need to define

Base.rand(::Type{Float_n}, ...)

or, in v0.7, you can use the Sampler mechanism.

I don’t think it is possible, and I don’t think it would be nice either, as Float_n above violates some basic assumptions about arithmetic, eg:

so an unsuspecting user would get unexpected behavior.

Good catch, wrong copy/paste. Edited now, because it would be distracting. Thanks !

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

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.

1 Like

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.

1 Like

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 ?

Me neither, just experimenting :wink:

This issue (already cited) was interesting

but in my use case, the fragility does not matter.

This would be greatly eased if defining a fallback type were possible.
(if a method is not found for Float_n, fall back to Float64)

struct Float_n

Base.:+(x::Float_n, y::Float_n) = Float_n(x.val + y.val)