Macro swipl_macros::predicates
source · predicates!() { /* proc-macro */ }
Expand description
Define foreign predicates written in rust for use in prolog.
The predicates!
macro takes an arbitrary amount of predicate
definitions. These definitions may be semidet or
nondet. Optionally, a visibility specifier like pub
may be used
to change the visibility of the generated functions. These
definitions look somewhat like ordinary rust functions. However,
their argument list is completely untyped, as each argument is
known to be a &Term
, except for the first argument which is a
context object. As there always needs to be a context to call a
predicate, this first argument is required, even if it is unused.
For each definition, a registration function will be
generated. This function will be named register_<name>
, where
name is the name of the defined predicate, and this function will
take zero arguments. After calling this function, the predicate is
registered and may be used from prolog code.
Each definition may optionally be annotated with
#[module("<module>")]
and/or #[name("<name>")]
To change the
module this predicate will be registered in, or the name of the
predicate in prolog. By default, the predicate name will be the
definition name, and the module will be the context module at the
time of generation. For foreign libraries, this context module is
whatever module did the first import of this library. Otherwise
it’s usually ‘user’.
Semideterministic predicates
The first kind of predicate that you can define is a semidet predicate. Semidet, or semideterministic, means that this predicate is only going to have one result, and it could either be success or failure. Note that this also covers the deterministic case - to implement a deterministic predicate, just ensure that your predicate does not fail.
Semidet predicates return a PrologResult<()>
, which also happens
to be the type returned by most of the functions in the swipl
library. This means you can easily handle failure and exception of
things you call using rust’s ?
syntax, or make use of the various combinators that are defined on context
objects and in the result
module.
Examples
predicates! {
semidet fn unify_with_foo(context, term) {
let atom = context.new_atom("foo");
term.unify(&atom)
}
#[module("some_module")]
#[name("some_alternate_name")]
pub semidet fn term_is_42(_context, term) {
let num: u64 = term.get::<u64>()?;
into_prolog_result(num == 42)
}
semidet fn just_fail(_context) {
Err(PrologError::Failure)
}
pub semidet fn throw_if_not_42(_context, term) {
let num: u64 = term.get::<u64>()?;
if num != 42 {
context.raise_exception(&term!{context: error(this_is_not_the_answer, _)})
} else {
Ok(())
}
}
}
To register these defined predicates, their register function has to be called:
register_unify_with_foo();
register_term_is_42();
register_just_fail();
register_throw_if_not_42();
Nondeterministic predicates
Nondet or nondeterministic predicates are a bit more complex to implement. Instead of just one block which returns success or failure, nondet predicates are implemented with two bodies, a setup block and a call block.
In the setup block, you create a state object which will be available in the call block. The call block is then called with this state object. As long as the call block returns true, the predicate call is considered to still have choice points and will be called unless the caller does a cut, which will clean up the state object automatically.
The state type
Nondeterministic predicate definitions require you to specify a
type argument as part of the function signature. This specifies
the type of the state object, and is required to implement the
auto-traits Send
and Unpin
.
Setup
The setup block is called at the start of a predicate
invocation. It is to return a PrologResult<Option<StateObject>>
,
where StateObject
is your state object type.
You can return from this block in three ways:
- Return an exception or failure. The predicate will error or fail accordingly and the call block will not be invoked.
- Return
None
. The call block will also not be invoked, but the predicate will return success. This is useful to handle predicate inputs which allow your predicate to behave in a semidet manner. - Return
Some(object)
. This returns a state object for use in the call block. After this, the call block will be invoked.
Call
The call block is called each time the next result is required from this predicate. This happens on the first call to this predicate (except if the setup returned early as described above), and subsequently upon backtracking. The call block is given a mutable borrow of the state object, and is therefore able to both inspect and modify it.
you can return from this block in three ways:
- Return an exception or failure. Thep redicate will error or fail accordingly, and the call block will not be invoked again.
- Return false, signaling that this was the last succesful call to this predicate.
- Return true, signaling that there’s more results available upon backtracking.
After exception, failure or returning false to signal the last succesful call, the state object will be cleaned up automatically.
Examples
predicates!{
nondet fn unify_with_bar_baz<Vec<String>>(context, term) {
setup => {
Ok(Some(vec!["bar", "baz"]))
},
call(v) => {
let next = v.pop().unwrap();
let atom = context.new_atom(next);
term.unify(&atom)?;
Ok(!v.is_empty())
}
}
nondet fn fail_early<()>(_context) {
setup => {
Err(PrologError::Failure)
},
call(_) => {
// We never get here
}
}
nondet fn succeed_early<()>(_context) {
setup => {
Ok(None)
},
call(_) => {
// We never get here
}
}
}