git @ Cat's Eye Technologies Specs-on-Spec / master tamerlane / tamerlane.markdown
master

Tree @master (Download .tar.gz)

tamerlane.markdown @masterview rendered · raw · history · blame

Tamerlane
=========

Chris Pressey  
Created Jan 29 2000

### Introduction to Tamerlane

Tamerlane is a "constraint flow" language. The point of its creation is
to attempt to break as many paradigmal stereotypes and idioms that I'm
aware of; at least, to make it tricky and confounding to pigeonhole
easily.

It has some concepts in it from imperative languages, some from
functional languages, some from dataflow and object oriented languages,
and some from graph rewriting and other constraint-based languages, and
they're all muddled together into a ridiculous *potpourri*.

Despite being such a mutt, there might actually be some algorithms that
would be dreadfully easy to write in Tamerlane.

### Overview of Tamerlane

A Tamerlane program consists of a mutably weighted directed graph.

Each node is considered to be an independent updatable store. The data
held in each store is represented by the weights of the arcs exiting the
node.

An arc of weight zero is functionally equivalent to the absence of an
arc.

### Description and Example

At this point we may introduce the syntax in a sample ASCII notation for
a simple, almost pathological Tamerlane program:

      Point-A: 1 Point-B,
      Point-B: 1 Point-C,
      Point-C: 1 Point-A.

The user of a Tamerlane program may submit *messages* to the program at
runtime. In this sense the user and the program are both objects which
share the symmetrical relationship **user-of/used-by**. The program
object's interface exposes a `query` method to the user, which is to be
considered runtime-polymorphic.

Using this message-passing mechanism, queries are submitted by the user
to a running Tamerlane program, much as queries would be submitted to a
running Prolog program.

Queries have their own syntax and semantics. Unlike Prolog, the user's
queries are interpreted as *rules*, perhaps accompanied by information
about where and when the rules are "introduced" into the graph.

As an example of a query that could be submitted to the above Tamerlane
program:

      1 Point-A -> 0 Point-A @ Point-A

This would introduce the rewriting rule

This rule is applied to the weights of the nodes in the graph starting
with the node specified after the `@` symbol. In this instance it would
start by trying to apply the rewrite to the node `Point-A`, but finding
`Point-A` to contain `1 Point-B`, nothing would change.

Each time a further query is submitted, each rule which has been
introduced into the graph, disappears from the node it was working on,
and is transmitted to the adjacent node with the lowest positive weight
value. If there is a tie for lowest weight, the rule is transmitted to
all of the adjacent nodes with the same lowest weight.

For efficacy we can consider the user able to submit a `nop` query. This
would not introduce any new rules into the graph, but it would cause all
active rules to propogate to new nodes on the graph.

So assume the user submits `nop`. The rule that was introduced by the
last query 'moves' from the node labelled `Point-A` to the node labelled
`Point-B` (since it's the only positive route out of `Point-A`.) It
tries to rewrite `Point-B`, but finding only `1 Point-C` in `Point-B`,
nothing happens.

Assume the user `nop`s again. The rule is propogated to `Point-C`.
Finally the pattern match succeeds, and `Point-C` is rewritten to a new
`Point-C`:

      Point-C: 0 Point-A.

After one more `nop`, the engine will generate a

      Rule stopped at Point-C (no adjacent nodes)

message back to the user. This uses the operation that the user object's
interface supplies to the running program, called `messageback`, which
the user must supply, but is, like `query`, considered
runtime-polymorphic.

Advanced Topics
---------------

### Rule Priority

Obviously, the user can enter more than one query in succession; s/he
doesn't need to explicity `nop` to wait for rules to resolve. Since the
graph can contain cycles, this would lead to a form of inherent,
synchronous concurrency: each time the `query` or `nop` methods are
invoked, more than one rule may be applied to the same node.

The order in which these competing rules is applied is based on the
rule's *priority*. Rules can be submitted with a specific priority in
the following manner:

      1 Point-A -> 0 Point-A @ Point-A ! 10

If there is a tie in priority when two rules are competing to rewrite
the same node, the outcome is guaranteed to be not simply undefined, but
rather, non-deterministic, or at least probabilistic.

The user can also specify a delay, measured in number of method calls
(`query`s or `nop`s) from the present query, at which the rule will be
introduced into the graph, like so:

      1 Point-A -> 0 Point-A @ Point-A in 10

And, for the sake of efficacy, the `nop` method on the program object
has overloaded syntaxes whose semantics are 'keep `nop`ing until some or
all rules have stopped.'

### Negative Weights

A negative weight from one node to another is interpreted in a rather
negative fashion with respect to the running program.

A negative weight is selected for propogation when it is closer to zero
(while still being non-zero) than any other weight (absolute value.)
When a negative weight is selected, however, the semantics of
propogation are different.

When a rule encounters an arc of the form:

      X: -1 Y.

The rule is not "just" copied to Y like "usual". Instead, it "rolls
back" the rule to Y in the fashion of a functional continuation.

All of the nodes that have been "touched" by this rule, since it was
last at node Y, are reset to their condition when the rule was last at
node Y. The rule itself is deleted from X and propogated to Y, and
rewriting continues from there.

If the rule has never visited Y, however, then such an exit arc is not
considered a valid candidate. It has an effective weight of 0
(non-existent.)

### Lambda Graphs

Lambda abstraction is a powerful form of referring to non-numeric
calculatory items such as functions and, in the case of Tamerlane,
graphs.

A user may submit a rule in the form

      1 X -> 1 Y ? Y: 1 Z, Z: 1 Y

Y is like a 'local variable' in this respect. When the rule is applied
to the node, and the pattern match succeeds, the nodes named Y and Z
need not actually exist, as they are simply created dynamically and
'attached' to the program graph.

Lambda graphs are subject to garbage collection, since they can only be
used by the rule that created them.

### Pigeonholes (Updatable)

Variables may be supplied which are explicitly updatable, and these are
termed pigeonholes. Pigeonholes acknowledge events. Their assignment is
associated with both nodes and arcs. They can occur when:

-   a rule enters a node
-   a rule leaves a node
-   a rule chooses an arc
-   a rule passes through an arc
-   a rule rewrites an arc

Updatable variables are always named after the node (or node.arc)
they're associated with, preceded by a `$` symbol. The data in the
variable is, of course, the arc weight (or in the case of nodes, the
node's internal key.)

If the pigeonhole is assigned the special value `%Cancel`, the rule is
cancelled from moving to the node/through the arc/rewriting the arc.

### Placeholders (Unification)

Variables may also be supplied as "placeholders". These are the same as
bindable (unifiable) variables in logical languages like Prolog. An
example of such might be:

      1 X 7 ^Y -> 7 X 1 ^Y

Which would replace "1 X 7 Foo" with "7 X 1 Foo", "1 X 7 Bar" with "7 X
1 Bar", etc.

### Horn Rules

Horn rules only succeed if all of their rules succeed. A Horn rule is
specified like:

      1 X -> 1 Y + 1 Z 0 G -> 1 G

(Remember that 0 G is equivalent to "the absence of an arc to G." If you
have a pattern like

      ^a G ^b G 0 G -> ^b Q

It will only match when there are exactly two different exit arcs to the
same node G, which is not disallowed (nor is a node with an exit arc
which points to itself.))

Note also that arcs are unordered amongst themselves. The rule

      1 A 1 B 1 C -> 1 E 3 D

is the same as

      1 B 1 A 1 C -> 3 D 1 E