Repository navigation
1.2 TopKit: Fields
Populations, partitions, and the algebra between them.
The Guide shows how one Agent gets its Tags. This guide is about the other direction: every Tag keeps the population of Agents that carry it, and a program can ask that population questions, walk it, partition it, and combine it with others. Everything here is one line long and runs; the blocks below are run by the test suite in order.
from TopKit import Post, Public, Record, Report, Tag
class Crew:
"""The host: a name and a pulse."""
def __init__(self, name):
self.name = name
self.alive = True
class Officer(Tag):
@Post
def Alive(agent):
return agent.alive
class Wizard(Officer):
@Record
def spells(agent):
return ["Light"]
class Fighter(Officer):
@Record
def weapon(agent):
return "sword"
class Sworn(Tag):
pass
ari, bo, cal, dee = Crew("Ari"), Crew("Bo"), Crew("Cal"), Crew("Dee")
Wizard(ari)
Wizard(bo)
Fighter(bo)
Fighter(cal)
Sworn(ari)
Sworn(cal)A Field is the population of a Tag, in the order they joined, weakly held: it never keeps an Agent alive. It comes in three views.
assert list(Wizard[:]) == [ari, bo] # everyone: the whole Field
assert list(Wizard) == [ari, bo] # the sound ones: every promise holds
assert list(~Wizard) == [] # the defective ones: a promise is broken
cal.alive = False # Cal breaks Officer's promise
assert list(Fighter) == [bo] # off the line
assert list(~Fighter) == [cal] # waiting for repair
assert cal in Fighter # still a member
assert cal in Fighter[:] # still in the FieldThe plain loop, for f in Fighter, is the working population. ~Fighter
is the repair queue. Fighter[:] is the roster. if Fighter: asks
whether anyone is fit; len(Fighter[:]) counts everyone.
Populations combine with three operators. | is either, & is both,
- is the left without the right. A Tag in an operator seat means its
sound population, exactly as it does in for w in Wizard.
assert list(Wizard | Fighter) == [ari, bo] # sound Wizards, then sound Fighters, each once
assert list(Wizard & Fighter) == [bo] # sound in both
assert list(Wizard - Sworn) == [bo] # sound Wizards who have not sworn
assert list(Fighter | Wizard) == [bo, ari] # order follows the left side firstThe whole Field and the defective view combine the same way, and the levels mix: a population is a population.
assert list(Wizard[:] | Fighter[:]) == [ari, bo, cal] # everyone who is either, Cal included
assert list(Fighter[:] - Wizard) == [cal] # every Fighter who is not a sound Wizard
assert list(~Fighter | ~Wizard) == [cal] # the whole repair queue of both
assert list(Sworn[:] & Fighter[:]) == [cal] # sworn, and a Fighter, of any condition
assert list(Sworn & Fighter[:]) == [] # Cal is defective, so he is not a *sound* SwornThat last line is worth a pause. Soundness is holistic: Cal broke
Officer's promise, so he is off every sound line, Sworn's included,
even though Sworn promised nothing itself. Ask the whole Field,
Sworn[:], when condition does not matter.
The result of an operator is not a list. It holds its two sides and reads them when it is walked, so it is never stale: an Agent who joins later is in it, and one who is repaired moves from one side to the other.
combatants = Wizard | Fighter # made once
assert list(combatants) == [ari, bo]
Fighter(dee) # Dee joins after the view was made
assert dee in combatants
cal.alive = True # Cal is repaired
assert cal in combatants
assert len(combatants) == 4
assert combatants # truth: anyone at all?Keep a combined view around like you would keep a Tag around. Do not copy it into a list unless you want the moment frozen.
Questions about Contributions. Use "name" @ agent with Python's
all, any, next or a comprehension to ask who has a Contribution.
See Presence: a name at an Agent
for examples over the whole Field (Tag[:]) and its sound population (Tag).
The battle loop. Every combatant regardless of class, fit to fight:
for fighter in Wizard | Fighter:
assert fighter.aliveThe roster of the missing. Who has a role but not the role that should go with it:
unsworn = (Wizard[:] | Fighter[:]) - Sworn
assert list(unsworn) == [bo, dee] # Bo and Dee never sworeThe repair queue across roles. One loop over every defective Agent of several Tags, whichever promise broke:
bo.alive = False
for broken in ~Wizard | ~Fighter:
broken.alive = True # sickbay
assert not list(~Wizard | ~Fighter)A gate that reads a population. A condition may ask a combined view a
question, since it answers in:
from TopKit import Pre, Precondition
class Champion(Tag):
@Pre
def Is_A_Fighting_Wizard(agent):
return agent in (Wizard & Fighter)
Champion(bo) # Bo is both
try:
Champion(ari)
except Precondition.Is_A_Fighting_Wizard:
pass # Ari is only a WizardPins combine too. A Pin's Field is a population of Tags, so the same algebra sorts Tags:
from TopKit import Flag, Pin
@Pin
class Combat(Tag):
pass
@Flag
@Pin
class Deprecated(Tag):
pass
Combat(Wizard)
Combat(Fighter)
Deprecated(Fighter)
assert list(Combat - Deprecated) == [Wizard] # combat Tags still in use-
No complement of a union.
~(Wizard | Fighter)has no universe to complement, so it is not defined.~Wizard | ~Fighteris what you mean. -
No order across sides.
|walks the left side first, then the right; it does not interleave by join time. -
No copies. A view reads the Fields it was made from. If you need a
frozen moment, say so:
list(Wizard | Fighter). -
Type unions still work.
Wizard | Noneis the language's own class union, untouched; only a Tag or a population on the other side makes the operator a Field operator.
-
Walk a Tag for the sound population,
~Tagfor the repair queue,Tag[:]for everyone. -
Combine with
|,&,-. A Tag in an operator seat is its sound population;Tag[:]and~Tagsay the other levels. - Keep views, not lists. A view is alive; a list is a moment.
-
Read a view in a condition with
in, and the gate follows the population.