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

source

#Documenter.Selectors.AbstractSelectorType.

Root selector type. Each user-defined selector must subtype from this, i.e.

abstract MySelector <: Selectors.AbstractSelector

abstract First  <: MySelector
abstract Second <: MySelector

source

#Documenter.Selectors.disableMethod.

Disable a particular case in a selector so that it is never used.

Selectors.disable(::Type{Debug}) = true

source

#Documenter.Selectors.dispatchMethod.

Generated function that builds a specialised selector for each selector type provided, i.e.

Selectors.dispatch(MySelector, 1)

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

source