Macro Cookbook
This chapter is a quick map of the macro surface. The goal is not to replace the deeper chapters, but to make the everyday question easy:
"I know roughly what I want to do. Which macro should I reach for?"
The macros fall into three layers:
- Schema definition:
attributes! - Fact construction:
entity! - Query construction:
find!,exists!,pattern!,pattern_changes!,path!,and!,or!,temp!,ignore!
Define attributes with attributes!
Use attributes! to declare typed attributes once,
then reuse them everywhere else.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::{GenId, ShortString}; attributes! { /// A person's display name. "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; /// Another person this person knows. pub friend: GenId; } } assert_ne!(social::name.id(), social::friend.id()); }
Reach for this macro when:
- you are defining a namespace or schema module
- you want attributes with stable ids and value schemas
- you want doc comments to become attribute metadata
If you already have attributes, you usually do not need attributes! in the
rest of the code you are writing.
Build facts with entity!
Use entity! when you want to create tribles for one
entity.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let alice = fucid(); let facts = entity! { &alice @ social::name: "Alice", }; assert_eq!(facts.root(), Some(alice.id)); assert_eq!(facts.len(), 1); }
If you omit the entity id, entity! derives one deterministically from the
attribute/value pairs.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let facts = entity! { _ @ social::name: "Alice" }; assert!(facts.root().is_some()); assert_eq!(facts.len(), 1); }
The macro also supports optional and repeated fields:
let aliases = ["Al", "A."];
let maybe_nickname = Some("Ace");
let facts = entity! { &alice @
social::name: "Alice",
social::nickname?: maybe_nickname,
social::alias*: aliases,
};
Reach for this macro when:
- you are constructing new data
- you want optional/repeated attribute ergonomics
- you want a deterministic derived id for a value object
Match facts with pattern!
Use pattern! to turn trible-shaped structure into a
query constraint.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::{GenId, ShortString}; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; "B74AA63539354CDA47F387A4C3A8D54C" as pub friend: GenId; } } let mut kb = TribleSet::new(); let alice = fucid(); let bob = fucid(); kb += entity! { &alice @ social::friend: &bob }; kb += entity! { &bob @ social::name: "Bob" }; let results: Vec<Id> = find!( friend: Id, pattern!(&kb, [ { alice.id @ social::friend: ?friend }, { ?friend @ social::name: "Bob" } ]) ).collect(); assert_eq!(results, vec![bob.id]); }
Inside a pattern:
?namerefers to a query variable from the surrounding query_?nameintroduces a local helper variable scoped to that pattern- literal expressions become equality constraints automatically
Use pattern! when you are querying the current contents of a TribleSet,
Checkout, or another pattern-capable source.
Query for results with find!
Use find! when you want rows back.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let alice = fucid(); kb += entity! { &alice @ social::name: "Alice" }; let names: Vec<Value<_>> = find!( name: Value<_>, pattern!(&kb, [{ _?person @ social::name: ?name }]) ).collect(); let first: &str = names[0].try_from_value().unwrap(); assert_eq!(first, "Alice"); }
There are three common shapes:
find!(value, constraint)for one projected variable as a bare valuefind!((a, b), constraint)for tuplesfind!((), constraint)when you care only that matches exist
Typed projections happen in the head:
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let alice = fucid(); kb += entity! { &alice @ social::name: "Alice" }; let ids: Vec<_> = find!( person: Id, pattern!(&kb, [{ ?person @ social::name: "Alice" }]) ).collect(); assert_eq!(ids, vec![alice.id]); }
Use ? on a projected variable when you want conversion failures as
Result<T, E> instead of dropping the row.
Ask existence questions with exists!
Use exists! when you only need yes/no.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::valueschemas::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let bob = fucid(); kb += entity! { &bob @ social::name: "Bob" }; let has_bob = exists!( pattern!(&kb, [{ _?person @ social::name: "Bob" }]) ); assert!(has_bob); }
You can also keep the typed-head form when the projection itself matters to the check:
let has_name = exists!(
(name: Value<_>),
pattern!(&kb, [{ ?person @ social::name: ?name }])
);
Use exists!(constraint) for pure existence checks instead of
find!((), constraint).next().is_some().
Match only new results with pattern_changes!
Use pattern_changes! for incremental
queries: matches are allowed to join against the full current state, but at
least one contributing trible must come from the change set.
for (work,) in find!(
(work: Value<_>),
pattern_changes!(&full, &delta, [
{ ?work @ literature::author: &shakespeare }
])
) {
// process only newly introduced matches
}
Reach for this macro when:
- you already have
fullanddelta - you want monotonic incremental processing
pattern!would re-emit old matches every time
See Incremental Queries for the full workflow.
Traverse edges with path!
Use path! when a relationship is recursive or
variable-length.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; attributes! { "B74AA63539354CDA47F387A4C3A8D54C" as pub friend: valueschemas::GenId; } } let mut kb = TribleSet::new(); let alice = fucid(); let bob = fucid(); let carol = fucid(); kb += entity! { &alice @ social::friend: &bob }; kb += entity! { &bob @ social::friend: &carol }; let results: Vec<(Id, Id)> = find!( (src: Id, dst: Id), path!(kb.clone(), src social::friend+ dst) ).collect(); assert!(results.contains(&(alice.id, bob.id))); assert!(results.contains(&(alice.id, carol.id))); assert!(results.contains(&(bob.id, carol.id))); }
The middle part is a small regular language over attributes:
- adjacency means concatenation
|means alternation*means zero or more+means one or more- parentheses group
Example:
path!(kb.clone(), start (social::friend | social::colleague)+ end)
Use path! when a fixed number of pattern! clauses would be awkward or
impossible.
Combine constraints with and! and or!
Use and! when every clause must hold:
find!(
(friend: Value<_>),
and!(
pattern!(&kb, [{ ?person @ social::name: "Alice" }]),
pattern!(&kb, [{ ?person @ social::friend: ?friend }])
)
)
Use or! when any branch may hold:
find!(
(alias: Value<_>),
or!(
pattern!(&kb, [{ ?person @ social::nickname: ?alias }]),
pattern!(&kb, [{ ?person @ social::name: ?alias }])
)
)
or! branches must mention the same variable set.
Introduce helpers with temp!
Use temp! when you need a fresh variable only inside a
sub-expression.
find!(
(person: Value<_>),
temp!((friend), and!(
pattern!(&kb, [{ ?person @ social::friend: ?friend }]),
pattern!(&kb, [{ ?friend @ social::name: "Bob" }])
))
)
This is useful when the helper participates in joins but should not be projected.
Hide helpers with ignore!
Use ignore! when a constraint needs internal
variables that should not be visible to the outer planner.
find!(
(person: Value<_>),
ignore!((friend),
pattern!(&kb, [
{ ?person @ social::friend: ?friend },
{ ?friend @ social::name: "Bob" }
])
)
)
This is a more specialized tool than temp!. Reach for it when you already
have a nested constraint that uses helper variables internally and you want to
hide them from the outer query.
Which macro should I use?
If you are:
- defining a schema: use
attributes! - building facts for one entity: use
entity! - matching trible structure: use
pattern! - matching only newly added results: use
pattern_changes! - asking for rows back: use
find! - asking for a boolean: use
exists! - traversing recursive edges: use
path! - requiring all clauses: use
and! - allowing alternatives: use
or! - introducing a fresh helper variable: use
temp! - hiding helper variables from the outer query: use
ignore!
From here, the best next stops are:
- Query Language for the execution model
- Patterns & Recipes for modeling patterns
- Incremental Queries for
pattern_changes!