For example, I have 4 types A, B, C and D, which can be filled in a parametric type P.

const U = Union{A, B, C, D}
struct P{F <: U, S <: U}
a::Vector
b::Vector
function P{F, S}(a, b) where {F, S}
F == S && throw(ArgumentError("The parameters must have different types!"))
end
end

However, what I want is that F cannot be equal to S. How can I do that if I do not want specify it in the inner constructor? What if I just want to inhibit statements like P{A, A}, P{B, B}, etc., even without parameters (a, b) because generating a and b can take time?

There’s no way to make it so that simply writing P{A, A} errors, if that’s what you want. If you call dump(P), you get

julia> dump(P)
UnionAll
var: TypeVar
name: Symbol F
lb: Core.TypeofBottom Union{}
ub: Union{A, B, C, D}
body: UnionAll
var: TypeVar
name: Symbol S
lb: Core.TypeofBottom Union{}
ub: Union{A, B, C, D}
body: P{F<:Union{A, B, C, D},S<:Union{A, B, C, D}} <: Any
a::Array{T,1} where T
b::Array{T,1} where T

This shows how UnionAlls like P are represented in Julia. You can have upper bounds (ub) and lower bounds (lb) on type variables, but UnionAll itself does not support more complicated constraints, probably to limit the computational complexity of type system queries (but I’m far from an expert on this subject).