Stepping Forward
This is an essay in the Rewriting History series. Please read at least Introducing Herodotus and Rewriting History before proceeding. This is also a followup to Stepping Back
The Challenge
Create the Thucydides Simulation Definition Language, or TSDL: A language for defining Fluents, Cancels, and Incidents in their archetypal form, along with causal relationships between these archetypes. Incident archetypes defined in such a way should be applicable to a Herodotus history via a Thucydides simulation. This essay will pay special attention to the TADL subset, which doesn't include the aforementioned causal relationships support.
Lingua Franca
To recap, an Incident is a container for some Fluents and some Cancels, occurring at some time, with a given set of bindings. It also has a "type". Its archetype provides functions for defining some of these dynamically. The way this looks in Herodotus is something like:
FluentArcs = [Full, Poorer],
CancelArcs = [CancelDiets],
Requirements = [ReqEnoughMoney],
I = i_arc:new(
eat_feast,
main,
FluentArcs,
["Owner", "MealCost"],
[Requirements],
[],%no extra input selectors
CancelArcs
)
This isn't wholly unpleasant, but defining Fluents sort of is:
Full = ?A_FLUENT(
{me, hunger},
_Time,
_Time + 6, %I'm hungry four hours later
1.0,
(_Now - _Time) / 6.0,
lists:max([0.0, _Net - _Value])
),
Tired = ?A_FLUENT(
... %more like the above
),
Happy = ?A_FLUENT(
... %more like the above
)
The irksome things about this ?A_FLUENT macro are that it's specific to the Erlang implementation, that it's order-dependent, and that it's ugly. Let's try and find a way around using it and the other macros, and make it more readable in the process.
%this defines a fluent "full", which decreases "hunger".
%We'll assume that hunger goes from 0 to 1 and is completely
%removed by the consumption of food.
%if this took an argument, it would be f full(Arg1, Arg2...)
f full:
%this is the fluent type.
%it gets the "Owner" variable from the bindings of the archetype
%in which it's triggered
type {hunger, Owner}.
%it starts immediately, so the "start" line is omitted.
%it defaults to "start Time."
%it ends effectiveness in six hours. this could default to "end infinity."
end Time + 6.
%it persists as 1.0. this could default to "persist undefined."
persist 1.0.
%and its value is the portion remaining of the food at the given time.
%The default is "value true."
value
(Now - Time) / 6.0
.
%it combines by clipping Net - Value to [0, 1].
%the default is "combine Value."
combine
lists:Max([0.0, Net - Value])
.
Note especially that the ugly _ variables are gone, bindings can be accessed just as if they were variables, and the syntax is a little cleaner overall. Even better, a lot of the definition can be omitted in the common case. Let's show another fluent, which reduces the money in the Owner's bank account:
f poorer:
type {money, Owner}.
value
MealCost
.
combine:
Net - Value
.
Next, we'll examine the "cancel search", which finds a set of fluents and cancels them. This is provided so that incident archetypes can cancel fluents without necessarily knowing exactly which ones they'll need to cancel beforehand.
%c defines a cancel search
%which detects fluents and
%generates cancels for them.
%in this case, we cancel any diets the Owner is on.
c cancel_diet:
%this binding also comes from the enclosing incident.
type {diet, Owner}.
%"start 0." and "end Time." are the default values,
%so we can omit them.
A mere two lines, much reduced from the original.
Requirements are another feature provided by Thucydides:
%r defines a requirement - in this case, the Owner
%must have enough money to buy the feast
r req_money_for_feast:
%a "history main." line could be added here, but it's the default
%this gets bindings too.
type {money, Owner}.
%"start 0." and "end Time." are the defaults.
%There could be an "args []." line in here, but we omit it
%since the list is empty.
%check is the value-checking function; it gets bindings, too.
%the default check function is "check Value == true."
%it's worth mentioning that type, start, and end will all be omitted,
%in which case only the check function will be used. This is generally
%used to verify properties of the Bindings.
check Value > MealCost.
This is a sight better than a six-argument ?REQ macro, surely!
The next feature we'll examine is the final element in the TADL subset of TSDL, the Incident Archetype:
%this is an incident archetype definition
i eat_feast(Owner, MealCost):
%We could say "histories [main].", but that's the default.
%refer to previously defined f_arc "full"
set full.
set poorer.
%we could define a new fluent inline with "set new_fluent(Arg): ... end.",
%then defining a fluent as above.
%refer to previously defined cancel
cancel cancel_diet.
%cancels can be defined inline too: "cancel unwanted_fluents: ... end."
%refer to previously defined req
require req_money_for_feast.
%requirements can be inline too: "require important_req: ... end."
This lightning tour of TSDL syntax bears some explaining. The basic element of TSDL is the "definition", which can be one of seven types:
- q
- An HQL query, just like in a .HQL file.
- f
- A fluent archetype
- c
- A cancel search
- r
- A requirement
- i
- An incident archetype
- b
- A binder function
- follow
- A follow definition
We haven't looked at binders and follows yet, but each definition has roughly the same form:
type name(arguments, if applicable):
parameter value.
...
subdefinition name(arguments, if applicable):
subdef-param value.
...
end.
parameter value.
...
The distinction between parameters and definitions is straightforward: the former begin immediately and end with a period, and the latter begin after a colon and end automatically (or, in the case of inlined definitions of types f, c, or r, with an end. tag. The end tag can be omitted for the other types when inlined). Sub-definitions can be nested.
The other thing to note is that virtually all of the parameters take functions of (Time, Bindings) as their arguments.
Since the definition of binders and follows have various semantic consequences, we'll leave the discussion of TSDL there for today.
Trigger Happy
Actually triggering incident archetypes, given bindings and time, is done by thucydides:trigger() in the local client interface and a similar message in the remote client interface. Triggering an incident also checks the requirements and triggers any incidents that might follow it at that particular time. Trigger can return three values:
- not_met
- The requirements for this incident aren't met at the given time.
- paradox
- Triggering this incident is incompatible with the future state of the world, and would cause a paradox. It isn't triggered.
- {ok, TriggeredIncidents}
- TriggeredIncidents is the list of incidents that were triggered as a result of triggering the given incident; this can happen if a follow is set up to immediately react to an incident. Follows will be described in a forthcoming article.
Hopefully, TSDL reads more plainly than raw Erlang, even though it incorporates some Erlang syntax. If there are any questions, please e-mail me or leave a comment below.
Labels: example, herodotus, theory, thucydides
Joe Osborn 2008-04-24
0 Comments:
Post a Comment
<< Home