Reputation: 8117
I would like to create a primitive type
that behaves like let's say Int64
, but named Foo
. Is this possible?
I learned here that I can do something like this
# Declare the new type.
primitive type MyInt8 <: Signed 8 end
# A constructor to create values of the type MyInt8.
MyInt8(x :: Int8) = reinterpret(MyInt8, x)
# A constructor to convert back.
Int8(x :: MyInt8) = reinterpret(Int8, x)
# This allows the REPL to show values of type MyInt8.
Base.show(io :: IO, x :: MyInt8) = print(io, Int8(x))
But I do not understand why, even if MyInt8
is a subtype of Signed
(MyInt8 <: Signed
) there are no methods for this type:
a = MyInt8(Int8(3))
b = MyInt8(Int8(4))
a + b
ERROR: promotion of types MyInt8 and MyInt8 failed to change any arguments
I thought that as a subtype the primitive type would automatically "get" all the methods of the supertype as well.
Where am I wrong here?
Upvotes: 3
Views: 202
Reputation: 20298
As a subtype of Signed
, MyInt8
does automatically "get" all methods defined for numbers. And there is actually quite a lot of them:
julia> methodswith(MyInt8, supertypes=true) |> length
1218
julia> methodswith(MyInt8, supertypes=true)[1:10]
[1] poll_fd(s::RawFD, timeout_s::Real; readable, writable) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:649
[2] poll_file(s::AbstractString, interval_seconds::Real) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:784
[3] poll_file(s::AbstractString, interval_seconds::Real, timeout_s::Real) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:784
[4] watch_file(s::AbstractString, timeout_s::Real) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:687
[5] watch_folder(s::String, timeout_s::Real) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:716
[6] watch_folder(s::AbstractString, timeout_s::Real) in FileWatching at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/FileWatching/src/FileWatching.jl:715
[7] randcycle(r::Random.AbstractRNG, n::T) where T<:Integer in Random at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/Random/src/misc.jl:348
[8] randcycle(n::Integer) in Random at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/Random/src/misc.jl:349
[9] randexp(rng::Random.AbstractRNG, ::Type{T}, dim1::Integer, dims::Integer...) where T in Random at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/Random/src/normal.jl:204
[10] randperm(r::Random.AbstractRNG, n::T) where T<:Integer in Random at /home/francois/.local/julia-1.5.2/share/julia/stdlib/v1.5/Random/src/misc.jl:282
Some of them don't require that MyInt8
conform to any specific interface, and will work "out of the box":
julia> a = MyInt8(Int8(3))
3
julia> conj(a)
3
But some will provide an implementation that depends on MyInt8
conforming to an interface:
# Here `imag` does not know how to build a zero of the MyInt8 type
julia> imag(a)
ERROR: MethodError: no method matching MyInt8(::Int64)
Closest candidates are:
MyInt8(::T) where T<:Number at boot.jl:716
MyInt8(::Int8) at REPL[7]:2
MyInt8(::Float16) where T<:Integer at float.jl:71
...
Stacktrace:
[1] convert(::Type{MyInt8}, ::Int64) at ./number.jl:7
[2] oftype(::MyInt8, ::Int64) at ./essentials.jl:367
[3] zero(::MyInt8) at ./number.jl:241
[4] imag(::MyInt8) at ./complex.jl:78
[5] top-level scope at REPL[37]:1
The addition falls into that last category:
# There is indeed a generic implementation that MyInt8 inherits
julia> which(+, (MyInt8, MyInt8))
+(a::Integer, b::Integer) in Base at int.jl:918
# But it relies on promotion features, which are not implemented for MyInt8
julia> a + a
ERROR: promotion of types MyInt8 and MyInt8 failed to change any arguments
Stacktrace:
[1] error(::String, ::String, ::String) at ./error.jl:42
[2] sametype_error(::Tuple{MyInt8,MyInt8}) at ./promotion.jl:306
[3] not_sametype(::Tuple{MyInt8,MyInt8}, ::Tuple{MyInt8,MyInt8}) at ./promotion.jl:300
[4] +(::MyInt8, ::MyInt8) at ./int.jl:921
[5] top-level scope at REPL[41]:1
In that particular case, the inherited implementation of +
is only there to handle the addition of two operands of different types; I guess you'll have to implement +
for MyInt8
yourself, in much the same way as Int8
implements its own addition:
julia> which(+, (Int8, Int8))
+(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:86
I'd probably go for an implementation like the following:
julia> Base.:+(a::MyInt8, b::MyInt8) = MyInt8(Int8(a)+Int8(b))
julia> a + a
6
EDIT:
This should reach the same performances as the native Int8
type:
# two Int8 vectors
julia> a1 = rand(Int8, 1000); b1 = rand(Int8, 1000);
# same vectors, as MyInt8 values
julia> a2 = MyInt8.(a1); b2 = MyInt8.(b1);
# check that both ways of doing the calculation produce the same results
julia> @assert a1 .+ b1 == Int8.(a2 .+ b2)
# Benchmark
julia> using BenchmarkTools
julia> @benchmark $a1 .+ $b1
BenchmarkTools.Trial:
memory estimate: 1.06 KiB
allocs estimate: 1
--------------
minimum time: 145.274 ns (0.00% GC)
median time: 176.402 ns (0.00% GC)
mean time: 200.996 ns (4.20% GC)
maximum time: 1.349 μs (79.34% GC)
--------------
samples: 10000
evals/sample: 759
julia> @benchmark $a2 .+ $b2
BenchmarkTools.Trial:
memory estimate: 1.06 KiB
allocs estimate: 1
--------------
minimum time: 140.316 ns (0.00% GC)
median time: 172.119 ns (0.00% GC)
mean time: 195.947 ns (4.54% GC)
maximum time: 1.148 μs (73.34% GC)
--------------
samples: 10000
evals/sample: 750
Upvotes: 3