Selectors
Documenter.Selectors
— ModuleAn extensible code selection interface.
The Selectors
module provides an extensible way to write code that has to dispatch on different predicates without hardcoding the control flow into a single chain of if
statements.
In the following example a selector for a simple condition is implemented and the generated selector code is described:
abstract type MySelector <: Selectors.AbstractSelector end
# The different cases we want to test.
abstract type One <: MySelector end
abstract type NotOne <: MySelector end
# The order in which to test the cases.
Selectors.order(::Type{One}) = 0.0
Selectors.order(::Type{NotOne}) = 1.0
# The predicate to test against.
Selectors.matcher(::Type{One}, x) = x === 1
Selectors.matcher(::Type{NotOne}, x) = x !== 1
# What to do when a test is successful.
Selectors.runner(::Type{One}, x) = println("found one")
Selectors.runner(::Type{NotOne}, x) = println("not found")
# Test our selector with some numbers.
for i in 0:5
Selectors.dispatch(MySelector, i)
end
Selectors.dispatch(Selector, i)
will behave equivalent to the following:
function dispatch(::Type{MySelector}, i::Int)
if matcher(One, i)
runner(One, i)
elseif matcher(NotOne, i)
runner(NotOne, i)
end
end
and further to
function dispatch(::Type{MySelector}, i::Int)
if i === 1
println("found one")
elseif i !== 1
println("not found")
end
end
The module provides the following interface for creating selectors:
Documenter.Selectors.AbstractSelector
— TypeRoot selector type. Each user-defined selector must subtype from this, i.e.
abstract type MySelector <: Selectors.AbstractSelector end
abstract type First <: MySelector end
abstract type Second <: MySelector end
Documenter.Selectors.disable
— MethodDisable a particular case in a selector so that it is never used.
Selectors.disable(::Type{Debug}) = true
Documenter.Selectors.dispatch
— MethodCall Selectors.runner(T, args...)
where T
is a subtype of MySelector
for which matcher(T, args...)
is true
.
Selectors.dispatch(MySelector, args...)
Documenter.Selectors.leaf_subtypes
— MethodReturn a list of all subtypes of T
which do not have further subtypes.
The returned list includes subtypes of subtypes, and it does not distinguish between concrete types (i.e. types which are guaranteed not to have subtypes) and abstract types (which may or may not have subtypes).
Documenter.Selectors.matcher
— FunctionDefine the matching test for each case in a selector, i.e.
Selectors.matcher(::Type{First}, x) = x == 1
Selectors.matcher(::Type{Second}, x) = true
Note that the return type must be Bool
.
To match against multiple cases use the Selectors.strict
function.
Documenter.Selectors.order
— FunctionDefine the precedence of each case in a selector, i.e.
Selectors.order(::Type{First}) = 1.0
Selectors.order(::Type{Second}) = 2.0
Note that the return type must be Float64
. Defining multiple case types to have the same order will result in undefined behaviour.
Documenter.Selectors.runner
— FunctionDefine the code that will run when a particular Selectors.matcher
test returns true
, i.e.
Selectors.runner(::Type{First}, x) = println("`x` is equal to `1`.")
Selectors.runner(::Type{Second}, x) = println("`x` is not equal to `1`.")
Documenter.Selectors.strict
— MethodDefine whether a selector case will "fallthrough" or not when successfully matched against. By default matching is strict and does not fallthrough to subsequent selector cases.
# Adding a debugging selector case.
abstract type Debug <: MySelector end
# Insert prior to all other cases.
Selectors.order(::Type{Debug}) = 0.0
# Fallthrough to the next case on success.
Selectors.strict(::Type{Debug}) = false
# We always match, regardless of the value of `x`.
Selectors.matcher(::Type{Debug}, x) = true
# Print some debugging info.
Selectors.runner(::Type{Debug}, x) = @show x