Matchers

Matchers #

This section describes all the matchers currently recognized by the forall section.

has #

has x, y, z

has passes when it finds a fact that matches the template. It can introduce new variables.

neg / missing #

neg x, y, z

neg passes when it cannot find any facts that match the template. It may not introduce new variables (because it doesn’t make say to say “let x equal something that doesn’t exist”).

maybe / optional #

maybe x, y, z

maybe passes whether or not it finds a fact that matches the template. It is only useful if it introduces a new variable.

none / ncc #

none {
  has x, y, z
  # ...
}

none creates a subchain of matchers and passes if that entire subchain yields an empty result set.

any #

any {
  option {
    has x, y, z
    # ...
  }
  option {
    has x, y, z
    # ...
  }
}

any passes if any of the subchains in options yield a non-empty result set.

assign #

assign :X do |token|
   # ...
end

assign always passes. The argument must be a new variable that will be bound to the value returned by the block.

assuming #

engine << rule("base") do
  # ...
end

engine << rule("specialization") do
  forall {
    assuming "base"
    # ...
  }
end

If several of your rules have a common prefix, assuming lets you extract it into a reusable base rule. The specialized rule will work as if the forall section of the base rule were included verbatim. This lets you keep your rules more compact and helps performance a bit, since some of the work can be reused.

The base rule must be installed prior to installing the specialized ones.

The rule compiler can collapse some simpler matchers like has into a single execution node if it detects that the declarations are identical, but for matchers taking code blocks, like assert, it is not possible, and assuming must be used.

Filters #

Filters are a category of matchers that block rule execution based on some predicate; they operate entirely on the token assembed so far.

Any argument to filter clauses can be a variable or a literal value, though usually you’d want at least one argument to be a variable.

same / eq / equal #

same x, y

same passes if its arguments compare as equal using #==. It’s recommended to only use it if both arguments are variables, because otherwise you can usually just match on the expected value directly with has.

diff / ne #

diff x, y

diff passes if its arguments do not compare as equal using #==.

As with same, it’s best to use this matcher when both arguments are variables. However, note that there is a subtle difference in behaviour between “there is no value X” and “there is a value that is not X”. To illustrate, suppose you have an item that is tagged with luxury and import, and you encode this with {item, tag, luxury} and {item, tag, import}.

Then, you might want to have a rule that captures non-imported items, so you can try saying:

forall {
  has :Item, :tag, :Tag
  diff :Tag, "import"
}

But this rule will erroneously match on your item, because it will latch on to the luxury tag, since all facts are processed independently of each other, and luxury is indeed not equal to import. This is the “there is a tag that is not X” scenario.

In this situation, what you want to do instead is neg :Item, :tag, "import" to say “there is no tag X”.

less #

less x, y

less passes if x < y.

greater #

greater x, y

greater passes if x > y.

in_list, not_in_list #

in_list value, list

not_in_list value, list

in_list passes if the second argument includes the first one; not_in_list passes if it does not.

assert #

assert { |token|
  # ...
}

assert :A, :B, :C, ... do |a, b, c, ...|
  # ...
end

assert passes if the block returns a truthy value.

Aggregates #

Aggregates calculate a value across all matching result sets and pass it forward. They all have the same base form:

aggregate :NewVar, over: :Var, map: ->(token) { ... }, using: ->(values) { ... }, partition: [:Var1, :Var2, ...]

NewVar is the variable that will receive the result of the aggregation.

over is the variable that is fed to the aggregation function. Alternatively, a map function with more complex logic may be provided.

using is the actual aggregation function.

partition splits all tokens into groups based on the provided variables. If no partitions are given, all tokens are lumped in the same group.

The execution order is tokens -> partition -> map/over -> using.

For convenience, the following matchers are defined with pre-declared using arguments:

count #

min #

max #

sum #

product #