Selectors

Documenter.SelectorsModule

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 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:

source
Documenter.Selectors.AbstractSelectorType

Root 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
source
Documenter.Selectors.dispatchMethod

Call Selectors.runner(T, args...) where T is a subtype of MySelector for which matcher(T, args...) is true.

Selectors.dispatch(MySelector, args...)
source
Documenter.Selectors.leaf_subtypesMethod

Return 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).

source
Documenter.Selectors.matcherFunction

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.

source
Documenter.Selectors.orderFunction

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.

source
Documenter.Selectors.runnerFunction

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`.")
source
Documenter.Selectors.strictMethod

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 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
source