I don’t think so, better code should be very easy to read.

It’s only hard to read because I went by trial and error. And some of it is really much simpler than writing out everything by ‘special case’. Extending the show method, for example, is so much easier and more powerful than writing a loop over an array. The subscript business is just an extra flourish, not needed.

If you remove the second type parameter, because it’s not needed, the code isn’t that complex.

But using a type parameter for the base is definitely the way to go.

julia> struct MyStruct
x::Int
end
julia> function f()
return MyStruct
end
f (generic function with 1 method)
julia> f()
MyStruct
julia> f()(5)
MyStruct(5)

Yes, the type of a struct is not generally used as a container for data in Julia in the same way you might use a class-level method or value in Python. However, it’s easy to recreate this effect with multiple dispatch:

julia> function my_property(::MyStruct)
return 5 # always 5 for any instance of `MyStruct`, effectively a static constant for this type
end
my_property (generic function with 1 method)
julia> my_property(MyStruct(1))
5

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(!)

With experience of other languages behaviour, it’s natural to ask if a new one can do the same and how. But of course I agree that C++ is C++ and Julia is Julia. And in turn I can ask the question : How Julia knows (guesses) that I want the sum and product of matrices with coeffs in F_p be the ‘usual’ sum and product of matrices?

All of the Julia matrix code boils down to operations on the elements. When you use a new element type, Julia will use the appropriate definitions for + and * for that element type. The reason Julia uses multiple dispatch is that it causes stuff like this to “just work”

Exactly. Julia knows how to sum over a matrix: start with the first element, add the second, then the third. And we have just shown Julia how to add integer modulo P.

Same with matrix product: start at one end, multiply, add, repeat. There’s nothing more to it. To improve performance, maybe a specialized matrix product is called for, but that’s just an optimization.

My guess is that it has to do with subtyping. You could imagine creating a struct at the top level, that is a “named struct type” that still allows dispatching on the name of the struct:

struct NamedTupleWithName{N, FN, FT}
name::N
fields::NamedTuple{FN, FT}
end
function make(name::Symbol)
NamedTupleWithName(Val(name), (x=1, y=2))
end
function property(s::NamedTupleWithName{Val{:Foo}})
s.fields.x + s.fields.y
end
function property(s::NamedTupleWithName{Val{:Bar}})
s.fields.x * s.fields.y
end

What you don’t have here is a way to encode a subtype relation between different types. For example, I don’t know how you would encode getx from below in the above scheme

abstract type Nonce end
struct Foo <: Nonce
x
y
end
struct Bar <: Nonce
x
y
end
function property(s::Foo)
s.x + s.y
end
function property(s::Bar)
s.x * s.y
end
function getx(s::Nonce)
s.x
end

What I meant was that I think your nul(k) function is a check for whether or not k is zero or not. Julia already has a generic method for this, which is called iszero. This iszero is used throughout Base in various library functions, so if you want to allow them to work with your number type, you can overload Base.iszero(k::F_p) = k.x == 0 instead.

Thank you for your example.
I understand that it’s the way to do it.
I’m not used to this syntax yet and BTW I still have some problems with Julia types.
I have to work on this ; for the moment it’s pure magic.
I hope you won’t mind if I come back to you for a few comments.

I think you spent already some time writing your examples. Since you accept to answer some questions I will ask one by one to reach a final comprehension of your code.
Till now I am making some experiments with it, changing this and that trying to generate errors and to interpret these errors. It’s an useful job, but I cannot make everything clear from such experiments.
I would like to be able to write again such kind of code understanding exactly what I’m doing and why.
So first let’s take the first line to begin with : struct Z{P} <: Integer
By this you describe type Z{P} to be a sub-type of Integer which is an abstract type.
Here are my questions :

What is the benefit doing this ?

What do you loose if you don’t do it ?

What if instead of Integer you choose something higher in the hierarchy such as Real or Number ?

What if you choose instead a primitive concrete type such as Int64 for example ?
Have a nice day !