Selectors
#Documenter.Selectors
— Module.
An 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 MySelector <: Selectors.AbstractSelector # The different cases we want to test. abstract One <: MySelector abstract NotOne <: MySelector # 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
The code generated by Selectors.dispatch(Selector, i)
will look similar 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
which would be further simplified after inlining matcher
and runner
as
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
— Type.
Root selector type. Each user-defined selector must subtype from this, i.e.
abstract MySelector <: Selectors.AbstractSelector abstract First <: MySelector abstract Second <: MySelector
#Documenter.Selectors.disable
— Method.
Disable a particular case in a selector so that it is never used.
Selectors.disable(::Type{Debug}) = true
#Documenter.Selectors.dispatch
— Method.
Generated function that builds a specialised selector for each selector type provided, i.e.
Selectors.dispatch(MySelector, 1)
#Documenter.Selectors.matcher
— Function.
Define 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
— Function.
Define 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
— Function.
Define 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
— Method.
Define 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 Debug <: MySelector # 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