# Parameterizing type by integer value using "where"

This simple GaloisField type is parameterized by the modulus `p` and the integer type `T`. It works fine, but note that the test on the type of `p` buried within an inner constructor.

``````using Primes

# Type definition: GaloisField{p,T}, where p is prime modulus, T is integer type
struct GaloisField{p,T} <: Number where {p, T<:Integer}
rep::T  # representative integer

# inner constructor
function GaloisField{p,T}(x::Integer) where {p, T<:Integer}
if !(typeof(p) <: Integer) || !isprime(p)
throw(ArgumentError("p must be a prime integer"))
end
return new(mod(x, p))
end
end
GaloisField{p}(x::T) where {p,T<:Integer} = GaloisField{p,T}(x)
``````

It would seem better to require that `p` be an integer in the type signature, like this

``````using Primes

# Type definition: GaloisField{p,T}, where p is prime modulus, T is integer type
struct GaloisField{p,T} <: Number where {p::Integer, T<:Integer}
rep::T  # representative integer

# inner constructor
function GaloisField{p,T}(x::Integer) where {p::Integer, T<:Integer}
if !isprime(p)
throw(ArgumentError("p must be a prime"))
end
return new(mod(x, p))
end
end
GaloisField{p}(x::T) where {p::Integer,T<:Integer} = GaloisField{p,T}(x)
``````

but that gives the error

``````syntax: invalid variable expression in "where" around In[2]:4

Stacktrace:
[1] top-level scope
@ In[2]:4
[2] eval
@ ./boot.jl:360 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
``````

Wouldnâ€™t this be better? Why doesnâ€™t it work?

And whatâ€™s with the syntax `const T2 = Array{Array{T, 1}, 1} where T` (from the UnionAll section of the Types in Julia Docs)? Am I supposed to read that as `...where T<:Any`? To me an unadorned `where T` is like an unfinished sentence.

The GaloisField code is an update of an example from a 2016ish talk by Andreas Noack.

1 Like

I think you want to write `where {p<:Integer, T<:Integer}` since `Integer` is an abstract type.

And whatâ€™s with the syntax `const T2 = Array{Array{T, 1}, 1} where T` (from the UnionAll section of the Types in Julia Docs)? Am I supposed to read that as `...where T<:Any` ? To me an unadorned `where T` is like an unfinished sentence.

I usually read it as â€śwhere `T` is some typeâ€ť which seems to be the same as being a subtype of `Any`, but I donâ€™t really know so maybe there could be something more intricate going on.

You cannot constrain the type of non-type parameter with where. Only type parameters can be constrained with where.

T is a type and the constraint T<:Integer signifies T must be a subtype of Integer
while p is a value of type Integer but this kind of constraint (type of value is ) cannot be enforced by where.

1 Like

Sorry, I should have clarified that the type is parameterized by the integer value of p. E.g.

``````julia> typeof(GaloisField{3,Int64}(1))

GaloisField{3, Int64}
``````

This reflects the mathematics: the GaloisField with modulus p is a number system with p elements. Arithmetic is defined between elements of the same GaloisField, but, for example, you canâ€™t add an element of a GaloisField with modulus 3 to an element of a GaloisField with modulus 5. So the type is parameterized by the value of the integer, as well as the integer type.

the integer `3` you see is not <:Ineteger:

``````julia> Array{Any, 3} <: Array{<:Any, <:Any}
true

julia> Array{Any, 3} <: Array{<:Any, <:Integer}
false

julia> Array{Any, Int64} <: Array{<:Any, <:Integer}
true
``````

`<:Integer` means Int64, Int32 etcâ€¦

Yes, thatâ€™s why I want to specify `where{p::Integer,T<:Integer}` and not `where{p<:Integer,T<:Integer}`.

Letting go of some explicit `Integer` constraints, internalizing validations:

``````
julia>
struct GaloisField{p,T} <: Number
rep::T  # representative integer
# inner constructor
function GaloisField{p,T}(x::Integer) where {p,T}
if !isprime(p)
throw(ArgumentError("p must be a prime"))
elseif !(T <: Integer)
throw(ArgumentError("T must be <:Integer"))
end
return new{p,T}(mod(T(x), p))
end
end

julia> GaloisField{11,Int32}(8)
GaloisField{11, Int32}(8)
``````

or along the same lines, cleaner imo:

``````# this inner constructor is not called directly
struct GaloisField{p,T} <: Number
rep::T  # representative integer
# inner constructor
function GaloisField{p,T}(x::Integer) where {p,T}
return new{p,T}(mod(T(x), T(p)))
end
end

# this becomes the interface
function GaloisField(p::T, x::T) where {T<:Integer}
if !isprime(p)
throw(ArgumentError("p must be a prime"))
end
return GaloisField{p, T}(x)
end``````
1 Like

Yes, you can do this, but relying on internal, user-defined type-checking seems harder to read & understand and more error-prone than declaring type constraints up front and letting the compiler enforce them.

Ah, I like your â€ścleaner imoâ€ť solution, having the user interface be `function GaloisField(p::T, x::T) where {T<:Integer}`.