Stepping Back

This is an essay in the Rewriting History series. Please read at least Introducing Herodotus and Rewriting History before proceeding.

The Challenge

Start moving towards Herodotus-qua-platform. Quit focusing so much on Erlang source code. The last essay was too Erlang-centric, whereas Herodotus and Thucydides are meant to be platforms themselves. Therefore, I'll be moving to a new notation for this essay, and hopefully it will hold.

The Changes

Before going on to the syntax, I'd like to explain some recent changes to the semantics. In the development of the RPS military simulation, I encountered some semantic knots of Herodotus that I thought were worth untangling. Mainly, I looked to simplify, improve, and more completely define the matching rules.

A Match Made in Heaven

The new matching rules are much more strongly defined, and are described below. The match selector is represented by Try and the value to check against by Value.

Atoms
Atoms are indivisible, distinct values, like symbols in Smalltalk or atoms in Lisp. Numbers are also treated as atoms. They are written as atom_name. They match against the three types in the following ways:
Atoms
Try == Value
Tuples
Try matches some V in Value
Lists
Try matches some V in Value
Tuples
Tuples are simple data structures. They are written as {el1, el2, el3} and any element type can be placed in them. Their meaning is roughly "all", and they match against the three types in the following ways:
Atoms
Some T in Try matches Value. This is a minor exception, but it's there for convenience. The other possible behavior is virtually useless.
Tuples
All T in Try match Value
Lists
All T in Try match Value
Lists
Lists are sequences of values. They are written as [el1, el2, el3] and any element type can be placed in them. Their meaning is roughly "any", and they match against the three types in the following ways:
Atoms
Some T in Try matches Value
Tuples
Some T in Try matches Value
Lists
Some T in Try matches Value

These new matching rules have two interesting consequences:

In short, it allowed me to simplify and more easily explain the matching rules, remove a fair amount of closely similar code, reduced the amount of work to be done by a simulation designer in the simple case, and permitted much more complex and interesting queries and definitions at a stroke.

This semantic change is the most significant improvement since the shift to Herodotus/Erlang.

Just Cause

Another semantic addition to the tools available in Herodotus/Thucydides is the concept of causality. A "Follow" is a simulation construct which describes cases where one or more incident archetypes can be triggered based on specific previously triggered archetypes, one or more conditions, or both. A Follow also describes how bindings are obtained for the incident to be triggered and the requirements to be checked. Finally, a Follow encodes some information about when it begins checking said conditions, and for how many units of time.

The semantic of Following is slightly more complex than the above lets on, but this understanding is sufficient to be able to talk about it. Another feature implemented alongside Follows is time. Simulations now maintain a "current time" which can be ticked forward by clients to arbitrary points at arbitrary resolution. Backwards ticking and Follows have not been thoroughly tested and, frankly, the semantic is unclear. I recommend against combining Follows with time travel. If a simulation has need of such a feature, it can be implemented in Herodotus client code.

Syntactically Delicious

What the above semantics, along with past work, indicate is that any syntax for Herodotus must include the following:

And any syntax which expresses Thucydides must cover the above, plus:

So, let's split the work into defining two small, specific languages:

Further, we'll posit that a set of simple operations on dictionaries, tuples, lists, atoms, and numbers is available to all of those languages.

The rationale for splitting the grammar this way is that HQL is meant to be used not just within a simulation or fluent definition, but also independently by client code, and the TSDL is how a client defines a simulation with archetypes, requirements, causality rules, &c. Certainly, a subset of TSDL could be defined which only considers archetypes; this can be called TADL, or Thucydides Archetype Definition Language, and would be useful to client code that doesn't wish to use Thucydides's causality mechanisms. Since Erlang is a little obtuse to the unfamiliar, a set of small, specialized domain-specific languages will be introduced.

One more note before continuing on: There is a case where client code might want to use Herodotus directly, ignoring Thucydides outright. However, this is really only useful for local clients (which will be explained below). Even in their case, they will likely make use of Thucydides's incident archetype indexing and triggering mechanisms; so there's no reason to have a Herodotus Incident Definition Language apart from TADL/TSDL.

Herodotus Query Language

The goal of Herodotus Query Language is to provide a compact syntax for defining queries and operations on the results of queries. It has an additional goal of being easy to read and easy to implement. Initially I had thought of a syntax like this:


history-name fluent-type start end args
  

Like that, separated by spaces, where history-name is an optional atom, defaulting to "main"; fluent-type is an atom, a list of fluent-types, or a tuple of fluent-types; start is an optional number, defaulting to zero; end is a number; and args is an optional list of arguments, defaulting to the empty list. Unfortunately, this syntax has some problems: it's not especially compact or easy to read, it's too whitespace-sensitive, and it's not obvious how to perform complex expressions. With a little thinking, I came up with this compact form:


history-name:fluent-type@start--end&args
  

This replaced spaces with symbols, but it still wasn't satisfying because its syntax was so weird. In the end, I decided to go with a subset of Erlang's syntax:


q(history_name, {fluent_type, start, end}, args)
  

With the same optional values as mentioned above. The advantage of this syntax is obvious in this example which finds the net worth of a person named Bill:


NetWorth = q({{value, q({{assets, bill}, 10})}, 10})
  

This compound query can be passed in at-once. To explain: the inner query q({{assets, bill}, 10}) gives the list of item identifiers in the possession of bill at time 10. The outer query finds the value of all of those item identifiers at time 10. This kind of combination of queries is possible with this third syntax, which also lends itself more readily to variable binding and pattern matching.

Herodotus client code comes in two flavors: Local clients and remote clients. Local clients run their own embedded Herodotus process; remote clients connect to a Herodotus server. A local client is written in or bridged to Erlang, and can dynamically create and trigger arbitrary queries. But a remote client can be written in any language, and has to limit these abilities for security reasons. Therefore, HQL statements can be prepared beforehand:


%in local client code:
hql:s(stored_query_1, fun() ->
  %...hql statements...
end)

hql:s(stored_query_2, fun(Arg1, Arg2) ->
  %...hql statements...
end)

%or in a .hql file for local or remote clients:
stored_query_1():
  %...hql statements...
  .

stored_query_2(Arg1, Arg2):
  %...hql statements...
  .
  

Queries stored in such a way are triggered with:


q(stored_query_name, args)
  

Where args is a list. The usage of HQL files for remote clients means that Herodotus server admins can vet the queries that are to be made and ensure that nothing damaging or harmful will be done. Hooks will also be provided to allow some automatic security auditing of HQL queries.

A final note to make on HQL is how HQL queries are invoked. In local client code, they're wrapped in an hql:e() function call, which itself takes a fun as argument. This is similar to Mnesia's transaction function. In remote client code, stored HQL queries are invoked by remote messages.

Labels: ,

Joe Osborn 2008-04-21

0 Comments:

Post a Comment

<< Home