Repository navigation
1.0.P6 Pattern: Preconditions
When you want a Tag that not everyone may take. Think of a door with a
bouncer. Before a Tag is applied, the Tag looks at the Agent and decides:
in, or not. That look is a Precondition, written with @Pre. It is a
function that receives the Agent and answers True (come in) or False
(stay out).
class Wizard(Tag):
@Pre
def Can_Study(agent):
return agent.level >= 1
class War_Caster(Tag):
@Pre
def Is_A_Caster(agent): # synergy: needs another Tag
return agent in Wizard
bruk = Character("Bruk", level=2)
try:
War_Caster(bruk)
except Precondition.Is_A_Caster:
pass # Bruk is no caster: nothing changed
assert bruk not in War_Caster
Wizard(bruk)
War_Caster(bruk) # now it takesThree things to notice.
- A refused Agent is untouched. When a Precondition says no, the tagging stops before anything is written. Bruk is exactly as he was.
-
The error carries the name you wrote.
except Precondition.Is_A_Castercatches that one refusal and nothing else.except TagPreconditionErrorcatches every refusal. Both work; the named one reads better. A name you never declared, such as a typo, is an error at theexceptline, so a silent handler that never fires cannot happen. -
A gate can ask about other Tags.
agent in Wizardis an ordinary check, so a Tag can require another Tag. That is how synergies are written: the second Tag needs the first.
Inputs travel with the tagging. Sometimes the gate needs information
that is not on the Agent yet, like a code number. You give it at the call:
MI6(bond, code="007"). TopKit then hands code, by name, to every
Precondition, Record builder and Imprint of that Tag that has a parameter
called code. The usual use is a Record that simply keeps the input:
class MI6(Tag):
@Pre
def Has_A_Code(agent, code):
return code is not None
@Record
def code(agent, *, code): # keyword-only: "this comes from the call"
return code
bond = Character("Bond")
MI6(bond, code="007")
assert bond.code == "007"Read the * as a blank space. A Record's function has three places, in
order: the Agent, what was already stored under that name (pattern 3),
and what the call brings. Here nothing was stored, so the star holds that
seat empty: def code(agent, *, code) says "agent, nothing stored, then
code from the call". If you wrote def code(agent, code), Python would
put code in the stored seat, and TopKit refuses that at tagging time
and prints the spelling above. Preconditions and Imprints have no stored
seat, so there def Has_A_Code(agent, code) is enough.
Watch out. A gate must answer True, False, or nothing at all.
Do not return a number and hope: a count of 0 is not False to TopKit,
and it will refuse the raw value. Write the comparison you mean, such as
return agent.slots > 0. This keeps a real zero from being mistaken for
a refusal.