Right, I'm pleased to say that I've now implemented enough of the Fjord language on Node.js to be able to run the Instrument example that I introduced it with. As yet, this runs in memory only - i.e., no disk, no network.

Here's the code on GitHub with tests that show how it works. The language has changed a little so I'll show the example here again, copied over from the test code, in order to explain the differences.

Starting with the instrument rules:

inrl1=WebObject.create('{ "tags": [ "equity", "instrument" ],'+
                       '  "%owid": "/$this/",'+
                       '  "%refs": { "tags": [ "equity", "bid" ],'+
                       '             "%owid": "/$bid/",'+
                       '             "on": "/$this/" },'+
                       '  "buyers": "/array/has($bid)/" }');
// -------------------------------------------------------------------
inrl2=WebObject.create('{ "tags": [ "equity", "instrument" ],'+
                       '  "%owid": "/$this/",'+
                       '  "%refs": { "tags": [ "equity", "ask" ],'+
                       '             "%owid": "/$ask/",'+
                       '             "on": "/$this/" },'+
                       '  "sellers": "/array/has($ask)/" }');

These are the rules that add - or, rather, ensure the presence of - referring bids and asks in the instrument's buyers and sellers lists. The big difference here is that referrers are now accessed through the '%refs' tag. Both '%refs' and '%owid' are available on all Fjord objects: %owid is the Object Web ID of the object, and %refs has a list of the OWIDs of all currently referring objects.

But, of course, you can't see the list or the OWIDs under %refs! This rule shows how Fjord can match single items to lists of items, and how it transparently jumps any OWIDs it comes across, to continue matching. In this case there is a pattern for a bid or ask object that refers to the instrument - notice the "on": "/$this/" match.

Every object in %refs must have some such link to the referred-to object that triggered the referral in the first place. This referrer becomes an observer: its rules are run again if any object it refers to changes.

More instrument rules:

inrl3=WebObject.create('{ "tags": [ "equity", "instrument" ],'+
                       '  "buyers":  { "price": "/$bids;number/" },'+
                       '  "bid-ask-spread": { "high-bid": "/number/max($bids)/" } }');
// -------------------------------------------------------------------
inrl4=WebObject.create('{ "tags": [ "equity", "instrument" ],'+
                       '  "sellers": { "price": "/$asks;number/" },'+
                       '  "bid-ask-spread": { "low-ask":  "/number/min($asks)/" } }');

These are the rules that set the bid-ask-spread of the instrument from the prices of the bids or asks in each list. It shows how, when a single match instance binds to a list, it can give a match set in any bound variables - here, '$bids' and '$asks' - which can then be reduced - here, using 'max()' and 'min()'.

The rules are now separated into two, as there was a historic first ever bug in public Fjord code: the original rule wouldn't match unless at least one buyer and one seller were present, and we're adding them one by one from scratch this time!

Here is the actual instrument object:

inst=WebObject.create('{ "tags": [ "equity", "instrument" ],'+
                      '  "long-name": "Acme Co., Inc",'+
                      '  "buyers": [ ],'+
                      '  "sellers": [ ],'+
                      '  "bid-ask-spread": { "high-bid": "10.0", "low-ask":  "20.0" } }',
                      [ inrl1, inrl2, inrl3, inrl4 ] );

Notice the list of rules that the object will be animated by: inrl1, etc., which are the rule object OWIDs, not the actual rule objects. The bid-ask-spread is set to some seed values.

On to bid and ask rules:

bidrule=WebObject.create('{ "tags": [ "equity", "bid" ],'+
                         '  "on": { "tags": [ "equity", "instrument" ],'+
                         '          "bid-ask-spread": { "high-bid": "/$hibid;number/" } },'+
                         '  "price": "/null/fix(2, $hibid * 1.10 )/" }');
// -------------------------------------------------------------------
askrule=WebObject.create('{ "tags": [ "equity", "ask" ],'+
                         '  "on": { "tags": [ "equity", "instrument" ],'+
                         '          "bid-ask-spread": { "low-ask": "/$loask;number/" } },'+
                         '  "price": "/null/fix(2, $loask * 0.90 )/" }');

Here we have rules for setting the price of a bid or an ask as a ratio of the instrument's bid-ask-spread numbers. These are not meant to be realistic business rules of course - they're for illustration purposes. The only real difference here is fixing the result to 2 decimal places, since Javascript was adding ugly rounding errors.

Now we go ahead and create two bids and two asks, pointing at the instrument by simply including its OWID:

bid1=WebObject.create('{ "tags": [ "equity", "bid" ], "on": "'+inst+'", "price": "" }', [ bidrule ]);
bid2=WebObject.create('{ "tags": [ "equity", "bid" ], "on": "'+inst+'", "price": "" }', [ bidrule ]);
ask1=WebObject.create('{ "tags": [ "equity", "ask" ], "on": "'+inst+'", "price": "" }', [ askrule ]);
ask2=WebObject.create('{ "tags": [ "equity", "ask" ], "on": "'+inst+'", "price": "" }', [ askrule ]);

This fires off all the rules and notifications .. and rules and notifications .. giving the final result:

exp=new WebObject('{ "tags": [ "equity", "instrument" ],'+
                  '  "long-name": "Acme Co., Inc",'+
                  '  "buyers":  [ "'+bid1+'", "'+bid2+'" ],'+
                  '  "sellers": [ "'+ask1+'", "'+ask2+'" ],'+
                  '  "bid-ask-spread": { "high-bid": "12.1", "low-ask":  "16.2" } }')
// -------------------------------------------------------------------
test.objectsEqual("Instrument example works", Cache[inst], exp);

Here the buyers and sellers correctly list the referring bids and asks, and the bid-ask-spread has been set to the values you get after two ratios are applied to the original seed numbers.

Rewrite rules are run on object construction, they're run whenever an object has a new referrer in its 'refs' list, and also whenever another object that's being referred to changes. The latter isn't exercised in this example, as all events are propagated by referral alone. The tests of Fjord have examples of the full Observer Pattern in operation.

 

Next Up

Now, this is living code, so it will no doubt be different again, soon. Watch this space, and follow me on Twitter, to keep up to date with developments.

Next, I'll be working on simple disk persistence of Fjord JSON objects and rules, followed by distribution in the FOREST style...