Not directly in type parameters because while a struct type’s structure cannot change, N+1 depends on what the dispatched method of + does, which is allowed to change at runtime. Even though changing the behavior of +(::Int, ::Int) specifically would break Julia, it can’t recognize an exception for a particular call signature. This would be hypothetically possible for a function that can’t change, but we can only define the changing generic functions, and none of the built-ins are supported. For now (and probably very far into the future because of practical issues I haven’t mentioned), the language doesn’t do type parameter computation.
However, you can enforce a relation between type parameters at runtime by checking it in all constructor methods (or rather the inner ones that all the others use), throw an (informative) error instead of allowing instantiation to complete. (EDIT: see next commenter’s example.) There are unsafe ways to get around it to make invalid instances, but undefined behavior is not your problem.
If you don’t enforce that relation, you can also indirectly dispatch to a different method given that relation by mapping subsets of the parametric type to Holy traits. I don’t know a good name so I’ll just go for a typical stand-in:
So all mystruct{N,M} where M==N+1 are mapped to Foo{true}(), and the rest are mapped to Foo{false}(). We can dispatch over those values’ different types to different methods:
Thanks,
I was already using Holy traits but I didn’t realized that one can perform operations on parameters of a parametrized method as in Foo{M==N+1}() .
I found a bit annoying to add the bar(x::mystruct) = bar(isfoo(x), x) kind function for every methods and I was looking for some kind of alias. But I guess I have my answer. I’ll mark this answer as a solution noting that @barucden message is a valid solution either.
FWIW, I would do what barucden posted. I don’t use Holy traits much and when I do, it’s not to specialize behavior for a specific relationship among type parameters. I either always enforce a relation or I don’t, but that’s just my experience, and I have very little.
Let me add that Benny’s response and mine complement each other.
If an instance of mystruct{M, N} is valid even in the case of M + 1 \neq N and you are just looking for a way to clearly dispatch on the two cases, M + 1=N and M + 1\neq N, then Benny showed how to do it.
If, on the other hand, it is only allowed to have mystruct{M, N} such that M + 1 = N, then my answer showed how to disallow construction of invalid instances.
Indeed, narrowing the span of possible parameters using inner constructor is quite important and should probably highlighted somewhere in the doc, especially when the structure tends to have a large number of parameters to stay as concrete as possible.
Narrowing parameters specifically isn’t described in the documentation for inner constructors, but enforcing invariants in general is. The doc’s current example enforces the inputs x, y to obey x <= y for instantiation, so it’s a small hop to doing that for the method’s static parameters, as barucden demonstrated.