So, it never really occurred to me that elegance was the enemy of true mathematics But anyway, I just wanted to show a simpler example:
struct Z{P} <: Integer
x::Int
Z{P}(n::Integer) where {P} = new{P}(mod(n, P))
end
Base.promote_rule(::Type{<:Number}, ::Type{Z{P}}) where {P} = Z{P}
Base.show(io::IO, z::Z) = show(io, z.x)
Base.:+(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x + b.x)
Base.:-(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x - b.x)
Base.:-(a::Z{P}) where {P} = Z{P}(-a.x)
Base.:*(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x * b.x)
Base.div(a::Z{P}, b::Z{P}) where {P} = Z{P}(a.x ÷ b.x) # I don't think this is needed, really
Now you can
jl> z = zeros(Z{7}, 3, 3)
3×3 Matrix{Z{7}}:
0 0 0
0 0 0
0 0 0
jl> one(z)
3×3 Matrix{Z{7}}:
1 0 0
0 1 0
0 0 1
jl> z = Z{11}.(rand(0:10, 3, 3))
3×3 Matrix{Z{11}}:
3 3 4
3 4 5
10 4 10
jl> z.^2 .+ 1
3×3 Matrix{Z{11}}:
10 10 6
10 6 4
2 6 2
jl> maximum(z.^2 .+ 1)
10
jl> (2 + 3im) * Z{11}(2)
4 + 6im
# etc.
I think comparison and random sampling is really neat, but whatever.
Base.:<(a::Z{P}, b::Z{P}) where {P} = (a.x < b.x)
Base.:<=(a::Z{P}, b::Z{P}) where {P} = (a.x <= b.x)
function Random.rand(rng::AbstractRNG, ::Random.SamplerType{Z{P}}) where {P}
return Z{P}(rand(rng, 0:P-1))
end
Anyway, if you are teaching this to students, you shouldn’t drag macros into it, and for all that is holy, don’t use global
(!)