# Restrictions on isbits type paremeter

In functions, you can put restrictions on the types of arguments like so:

``````function f(a::Array{T, 3}) where {T<:Real}
[ Function Body ...]
end
``````

Can you do something similar for type parameters that are not types, but `isbits` instances, like integers and floats? For example, I would have something like:

``````function f(a::Array{T, N}) where {T<:Real, N > 2}
[ Function Body ...]
end
``````
1 Like

Not really, but you can do something like this:

``````julia> @generated function f(a::Array{T,N}) where {T <: Real, N}
if N > 2
:(println("Hello World"))
else
:(throw(MethodError(f,(a,))))
end
end
f (generic function with 1 method)

julia> f(zeros(1))
ERROR: MethodError: no method matching f(::Vector{Float64})

Closest candidates are:
f(::Array{T, N}) where {T<:Real, N}
@ Main REPL:1

Stacktrace:
 macro expansion
@ .\REPL:1 [inlined]
 f(a::Vector{Float64})
@ Main .\REPL:1
 top-level scope
@ REPL:1

julia> f(zeros(1,2))
ERROR: MethodError: no method matching f(::Matrix{Float64})

You might have used a 2d row vector where a 1d column vector was required.
Note the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].
You can convert to a column vector with the vec() function.

Closest candidates are:
f(::Array{T, N}) where {T<:Real, N}
@ Main REPL:1

Stacktrace:
 macro expansion
@ .\REPL:1 [inlined]
 f(a::Matrix{Float64})
@ Main .\REPL:1
 top-level scope
@ REPL:1

julia> f(zeros(1,2,3))
Hello World

julia> f(zeros(1,2,3,4))
Hello World
``````
1 Like

`T<:Real` seems to imply `T` is substituted at runtime e.g. `Int` then dispatches `Int<:Real`, but the latter half does not happen. I’m not entirely certain because I can’t remember a way to interact with it, but it’s more like `T` is an instance of `TypeVar` so previous loophole attempts to involve more generic method calls failed. So, you’re restricted to `<:`, a built-in function you cannot change, and `>:`, which is a generic function but just forwards to `<:`. I’m not actually sure if that generic function itself is present in `where` clauses; as much as I change `Base.:>:`, it does not interfere with method definitions.

You also wouldn’t want a redefinable generic method to complicate dispatch anyway. For one, redefining `>(::Int, ::Int)` changes method dispatch and invalidates things. For another, it is not trivial to infer subsets of instances like you can with types. You can tell `T<:Int` takes priority over `T<:Real` because there is a scattered chain of `<:` declarations that lets the operation `Int<:Real` return `true`. How would you tell that `N > 5` is a subset of `N > 2`? We can tell at a glance because we know the intended meaning, but it’s another matter to communicate that to the method table. Brute force way is to evaluate over every possible instance and count. We could make an associated method for `>` specifically for quickly calculating subsets, but then it either relies on `>` never changing, which is not very generic at all, or involves nightmarish maintenance of distant places.

A comparatively simple way to establish priority is to list conditions in order, and while we can’t impose order on methods, we can do that with if-statements. I think constant folding can eliminate branches too, though generated functions are more of a guarantee.

You can verify validity (or do other `N`-dependent logic) within the function body. The branches are compiled away, at least in simple cases, you don’t need a `@generated` function for that.

``````julia> function f(a::Array{<:Real,N}) where {N}
(N <= 2) && error("Need more N")
println("Valid N")
end
f (generic function with 1 method)

julia> f(zeros(1, 1))
ERROR: Need more N
Stacktrace:
 error(s::String)
@ Base ./error.jl:35
 f(a::Matrix{Float64})
@ Main ./REPL:2
 top-level scope
@ REPL:1

julia> f(zeros(1, 1, 1))
Valid N

julia> @code_llvm f(zeros(1, 1))
;  @ REPL:1 within `f`
; Function Attrs: noreturn
define void @julia_f_316({}* noundef nonnull align 16 dereferenceable(40) %0) #0 {
top:
;  @ REPL:2 within `f`
call void @j_error_318({}* inttoptr (i64 139717740904624 to {}*)) #0
unreachable
}

julia> @code_llvm f(zeros(1, 1, 1))
;  @ REPL:1 within `f`
define void @julia_f_319({}* noundef nonnull align 16 dereferenceable(40) %0) #0 {
top:
;  @ REPL:3 within `f`
call void @j_println_321({}* inttoptr (i64 139717734901016 to {}*)) #0
ret void
}
``````

Notice that the LLVM is different for the two calls and does not have any branches. It directly calls `error` when you give a 2D array and `println` when you give a 3D array.

1 Like

Thanks!