Having managed to express your if typeof(v) by suitable dispatch calls, how do you check if they are correct or cover all possible cases?

Of course, like in other programming languages, one can always code test cases.
But having some more dispatches or not 1-liners, it’s easy to overlook a case.

For logic circuit design there are tools to prove or visualize the implemented logic.
Since the type system is huge but finite, I wonder if a clever type (…) has already thought of such an idea.

As a start,

fun(x::T1) = ...
fun(x::T2) = ...
fun(x) = ...

Q: How to find out what types the last (least specific) dispatch will catch?
A: It catches all the rest.

Q: Could it happen that two dispatches overlap, with unintended consequences?
A: Can happen, but watched by Julia’s ambiguous warning.

Q: Are there logic operations on types possible other than Union?
A: (no)

An untyped function argument is implicitly typed Any, so is able to be used with all types. If a different method with a more specific signature exists, it will be used instead, so it can be said that the function with the untyped argument will catch “everything that’s not more specific somewhere else”.

julia> f(a, b::Integer) = "1"
f (generic function with 1 method)
julia> f(a::Integer, b) = "2"
f (generic function with 2 methods)
julia> f(1,2)
ERROR: MethodError: f(::Int64, ::Int64) is ambiguous. Candidates:
f(a, b::Integer) in Main at REPL[1]:1
f(a::Integer, b) in Main at REPL[2]:1
Possible fix, define
f(::Integer, ::Integer)
Stacktrace:
[1] top-level scope
@ REPL[3]:1

I’m not sure what you mean by that. Where do you want to use the result?

No, such exclusions are not possible as far as I know. It’s also extremely niche to need this in the first place - you’re basically saying “all types work for this function, except X, Y and Z”, which feels weird for the vast majority of cases. To me it’s more about a method saying “I can accept V, W and U, and nothing else”.

You’d also have to write the method for the others as well anyway, leading to repetition in type signatures (and worse, if you update one definition you’ll have to update the other as well, quickly leading to cross-cutting changes where none existed before).

On top of that, what if a new type comes into scope that also doesn’t work for your method? Would you have to update it as well?

Makes sense, the two seemed to imply the same logic, but a type is general (global scope), the isstruct itself is placed in a (usually dispatched) function, thus being more selective. - Thx!

You can think of the julia type graph as having a Top type (of which all types are a subtype, this is Any) and a Bottom type (of which all types are a supertype, this is the empty Union{}). What you can do with dispatch is to select a given “path” from some type that’s <: Any (or whatever you specify in a signature) towards Union{}. If you select a union of types (i.e. f(a::Union{A,B}), you effectively allow multiple “paths” from subtypes of the elements of the Union to be used here. Note that A and B need not be concrete types! If they’re abstract types, all concrete subtypes of that abstract type are allowed (plus the empty Union, but no concrete object can have the empty union as its type).

Saying “except X” in that type graph would mean "All paths from Any to Union{}, but the path that begins at X" (which again feels a little weird - you’re effectively saying “no matter what you come up with, I can handle it”). At that point, you might as well use ::Any and use specialized methods for all other things you want to handle differently (and not repeat yourself).