Modifying TypeVar that will have a concrete value?

Say I have an abstract type that is parameterized on a TypeVar that will have a concrete integer value.

abstract type A{N} end

I have a struct B where I’d like to subtype A but where I modify the TypeVar that is used.

struct B{M} <: A{M+1}
...
end

Adding 1 is arbitrary. I might also want to multiply M or something like that.

Is it possible to replicate this behavior somehow? Doing it directly does not work, since you can’t constrain M to be of a particular type, like you would do in C++.

That’s impossible because subtyping and field types need to be unchanging, but methods aren’t.* Part of it is M not being constrained, so +(M, 1) can’t be dispatched in advance. But even if you could somehow specify M::ConcreteType, there’s no proof that the method for the call +(::ConcreteType, ::Int) won’t change, even if you are sure it shouldn’t change like in the case of +(::Int, ::Int). Imagine that you had B{3} <: A{4}, but Big Brother tells you 3+1 == 5; B{3} <: A{5} is an unfeasible change to make across all method tables and compiled code.

I think the closest you can do is struct B{M, N} <: A{N} end, throw an error in the inner constructor when N == M+1 is violated, and try to make sure it’s made true in other constructors e.g. B{M}() where M = B{M, M+1}(). Note that this doesn’t constrain N == M+1 for the type itself; with enough effort you can change what the constructors do, and the type parameters won’t need to (and can’t) change to accommodate the constructors’ different constraints.

*Example of constraint in changeable inner constructor
julia> abstract type A{N} end

julia> struct B{M, N} <: A{N}
         function B{M, N}() where {M, N}
           if !(N == M+1)  throw("blah")  end
           new{M,N}()
         end
       end

julia> B{3,5} # type itself not constrained
B{3, 5}

julia> B{3,5}() # but constructor constrains instantiation
ERROR: "blah"
Stacktrace:
 [1] B{3, 5}()
   @ Main ./REPL[12]:3
 [2] top-level scope
   @ REPL[15]:1

julia> struct B{M, N} <: A{N} # constructor can be changed
         function B{M, N}() where {M, N}
           if !(N == M+2)  throw("blah")  end
           new{M,N}()
         end
       end

julia> B{3,5}()
B{3, 5}()

julia> B{M}() where M = B{M, M+2}() # this UnionAll constructor helps constraint

julia> B{3}()
B{3, 5}()
1 Like

But do checkout GitHub - vtjnash/ComputedFieldTypes.jl: Build types in Julia where some fields have computed types

1 Like

Wow, that package does exactly what I want. I imagine that it essentially does what Benny suggested, but in a manner that is transparent to the user.

I appreciate the comprehensive response!

As a C++ programmer, it seems a bit…outlandish to suggest that 3+1 could ever be expected to yield a value other than 4, but such is the way of Julia’s type system, I suppose.

Thanks!

You sure? I’ve never managed to make it work on type parameters in the header alone:

julia> @computed struct B{M} <: A{M+1} end
ERROR: MethodError: no method matching +(::TypeVar, ::Int64)
...

Aside: be aware that ComputedFieldTypes implicitly introduces type parameters, much like B{M, N} earlier. Constructors are omitted so it appears as if only the declared parameters M exist, but B{M} won’t be the concrete type, which can be computed with fulltype(B{M}). If you change any of the methods used to compute the fields’ types, not everything can adjust to B{M} referring to a different type. It can be convenient syntactic sugar, but I ended up preferring not to hide the extra parameters and constructors.

+ can be assigned different functions in different modules without affecting each other, though a different name is probably more sane.

1 Like

You sure? I’ve never managed to make it work on type parameters in the header alone:

Oops, I should have tried it before commenting. :sweat_smile:

+ can be assigned different functions in different modules without affecting each other, though a different name is probably more sane.

Ahh, I see! OK, that actually makes a lot of sense. Thanks again for demystifying this stuff.