PatrickT
PatrickT

Reputation: 10540

Julia: Abstract and Concrete Types Integer versus Int8 versus Int64

Suppose I have an integer n that will only ever take values in [0, 10]. Should I declare it as n::Integer to be general about it, as n::Int8 or n::UInt8 to be parsimonious or n::Int64 for a 64 bit system?

Please clarify the reasons for a newbie, e.g. style, performance.

Reference: https://docs.julialang.org/en/release-0.5/manual/integers-and-floating-point-numbers/

Updated Reference (2021): https://docs.julialang.org/en/v1/manual/types/#man-abstract-types

Upvotes: 15

Views: 4374

Answers (3)

miha priimek
miha priimek

Reputation: 979

For a newcomer (like myself) reading the abstract type docs cleared things up. In addition, checking the following helped (<: reads 'is a subtype of'):

julia> Int<:Integer
true

julia> UInt<:Integer
true

and

julia> Int64<:Int
true

Upvotes: 2

Michael K. Borregaard
Michael K. Borregaard

Reputation: 8044

EDIT: refer to Stefan's accepted answer. I meant this is a response to the use of types in function dispatch, but in fact contradicted myself (as I do say explicitly that function dispatch should actually be Integer).

I'd always use Int, just for the generality of it, but it depends how performance-critical your application is. Never Int64 unless you explicitly need it. Many functions dispatch on Int rather than Integer(though the advice is to dispatch on abstract types), which means that they will fail when passed a UInt8 (because Int is a subtype of Signed and UInt isn't) so being overzealous with types will cause problems. As a general rule-of-thumb you should never be more specific with types than you have to.

Upvotes: 4

StefanKarpinski
StefanKarpinski

Reputation: 33290

It's important to distinguish two different cases.

Storage: If you have a type that stores n as one of its fields, or as a value in an array, then you should definitely consider using Int8 or UInt8. Even if the saved space for a single value is negligible, if many instances of your type are created and stored in a collection, then the space savings can rapidly become significant. Let's say you have a Foo type with a field n, then you might do this:

struct Foo
    n::UInt8
end

When a value is assigned to the n field of a Foo object, it will automatically be converted to UInt8 and an error will be raised if the value cannot be converted faithfully:

julia> Foo(123) # Ints are automatically converted to UInt8
Foo(0x7b)

julia> typeof(ans.n)
UInt8

julia> Foo(500) # if too large, an error is raised
ERROR: InexactError()
Stacktrace:
 [1] Foo(::Int64) at ./REPL[1]:2

julia> Foo(-1) # ditto if too small
ERROR: InexactError()
Stacktrace:
 [1] Foo(::Int64) at ./REPL[1]:2

julia> Foo(2π/π)
Foo(0x02)

If the value that's assigned is already of the correct type then no check is required so there's no overhead.

Dispatch: If you are writing a method of a function that takes n as an argument, then there's no harm in having as loose a type annotation on the n argument as makes sense semantically. In the case you've described, it seems that any kind of integer value would be sensible, so using n::Integer would probably be appropriate. For example, if wanted to implement a checked constructor for Foo objects, you could do this:

struct Foo
    n::UInt8

    function Foo(n::Integer)
        0 <= n <= 10 || throw(ArgumentError("n not in [0, 10]: $n"))
        return new(n)
    end
end

Now an error is thrown if a value outside of [0, 10] is given:

julia> Foo(123)
ERROR: ArgumentError: n not in [0, 10]: 123
Stacktrace:
 [1] Foo(::Int64) at ./REPL[26]:2

julia> Foo(3)
Foo(0x03)

This Foo construction works for any kind of integer, checking that it's in the correct range, and then converting to UInt8. This is slightly more restrictive than the built-in constructor for Foo, which will happily take any kind of n argument and try to convert it to UInt8 – even when the argument is not of an integer type. If that kind of behavior is desirable, you could loosen the type signature here further to n::Real, n::Number (or even n::Any, although that seems excessive).

Note that there is no performance advantage to tightly typed method arguments – the specialized code for actual argument types is generated on demand anyway.

Upvotes: 19

Related Questions