Nothing
is just a type with a single value: nothing
(all lowercase). It does not have any special behaviour that allows for skipping or ignoring parameters, but you can use it for this purpose (as you could use any other type, especially the empty ones). You can use them in both positional and keyword arguments but each use is considerably different.
In positional arguments:
julia> f1(a::Nothing,b::Number) = println("f(b)")
f1 (generic function with 1 method)
julia> f1(a::Number,b::Nothing) = println("f(a)")
f1 (generic function with 2 methods)
julia> f1(a::Number,b::Number) = println("f(a,b)")
f1 (generic function with 3 methods)
julia> f1(nothing, 10)
f(b)
julia> f1(10, nothing)
f(a)
julia> f1(10, 10)
f(a,b)
Basically, you can use it to mark which parameters you are not passing. My only annoyance with this solutions is that nothing
is too long of a word. You can use a gloabl const SKIP = nothing
or something like that to reduce the typing. Or even create a new empty type like struct S; end
but then you need to define and call the code the following way:
julia> f1(a::S,b::Number) = println("f(b)")
f1 (generic function with 1 method)
julia> f1(a::Number,b::S) = println("f(a)")
f1 (generic function with 2 methods)
julia> f1(a::Number,b::Number) = println("f(a,b)")
f1 (generic function with 3 methods)
julia> f1(S(), 10)
f(b)
julia> f1(10, S())
f(a)
julia> f1(10, 10)
f(a,b)
The use of nothing in keyword parameters is considerably different because you do the dispatch manually inside the function.
julia> function f1(; a = nothing, b = nothing)
if a !== nothing && b === nothing
println("f(a)")
elseif a === nothing && b !== nothing
println("f(b)")
elseif a !== nothing && b !== nothing
println("f(a, b)")
end
end
f1 (generic function with 1 method)
julia> f1(; a = 1)
f(a)
julia> f1(b = 2)
f(b)
julia> f1(; b = 1, a = 2)
f(a, b)
You can also combine the two approaches (positional and keyword), you cannot combine them at the same call, but you can call either way and gain the benefit you do not need the manual dispatch (the positional solution will do the dispatch for you). Note that if you pass a combination of parameters that do not exist to the keyword alternative it will accept it and pass it to the positional that will then error with MethodError
(I think this is good behaviour).
julia> f1(a::Nothing,b::Number) = println("f(b)")
f1 (generic function with 1 method)
julia> f1(a::Number,b::Nothing) = println("f(a)")
f1 (generic function with 2 methods)
julia> f1(a::Number,b::Number) = println("f(a,b)")
f1 (generic function with 3 methods)
julia> f1(; a = nothing, b = nothing) = f1(a, b)
f1 (generic function with 4 methods)
julia> f1(nothing, 1)
f(b)
julia> f1(1, nothing)
f(a)
julia> f1(1, 1)
f(a,b)
julia> f1(; a = 1)
f(a)
julia> f1(b = 1)
f(b)
julia> f1(; b = 2, a = 1)
f(a,b)
julia> f1()
ERROR: MethodError: no method matching f1(::Nothing, ::Nothing)
Closest candidates are:
f1(::Nothing, ::Number) at REPL[1]:1
f1(::Number, ::Nothing) at REPL[2]:1
Stacktrace:
[1] f1(; a::Nothing, b::Nothing) at ./REPL[4]:1
[2] f1() at ./REPL[4]:1
[3] top-level scope at REPL[13]:1