UNPKG

lib-jsl

Version:

JSL is a JSON based logic programming library meant for embedded use

467 lines (353 loc) 11.3 kB
[Full documentation](http://kavi-saralweb.github.io/docs/html/index.html) --- # JSL Overview JSL is a JSON based logic programming library. It is meant to be used as an embedded rules engine in JS applications. We introduce JSL with a simple object unification example that produces a contract out of a matching bid and offer. ``` var JSL = require('lib-jsl'); var offer = { offerer : 'sandeep', bidder : '$bidder', symbol : 'ABC', price : 10, qty : 100 } var bid = [{ offerer : '$offerer', bidder : 'kavi', symbol : 'ABC', price : 10, qty : 100 }] var jsl = new JSL({ rules: [bid], query: [offer] }); var response = jsl.run(); console.log('contract: ', response); module.exports = response; /* response [ [ { "offerer": "sandeep", "bidder": "kavi", "symbol": "ABC", "price": 10, "qty": 100 } ] ] */ ``` The bid and offer records are the same except that they both leave one of bidder / offerer as variables (indicated by string values which start with '$'). We use JSL to unify these two objects and produce a merged object with the same keys and all variables instantiated. The example shows some important characteristics of JSL. - JSL rules and query are nothing but (JSON serializable) JS objects. - A set of JSL rules is an array of array of objects. (This is why the bid variable is an array containing just one object). - Variables are allowed in both rules and query. Any value string which starts with '$' is a variable. - JSL execution proceeds by unifying the query object with rules. - JSL execution produces one or more fully instantiated versions of the given query object, i.e. all variables in the query are replaced with matching structures from the ruleset. <span id="unification"> </span> ## Unification We extend our example by introducing multiple bids, and asking the system to produce a contract for the one which matches a given offer. The bids variable is now an array of arrays, i.e. a valid JSl batch of facts (data). It can be given directly as rules to JSL. ``` var JSL = require('lib-jsl'); var offer = { offerer : 'sandeep', bidder : '$bidder', symbol : 'ABC', price : 20, qty : 100 } var bids = [ [{ offerer : '$offerer', bidder : 'kavi', symbol : 'ABC', price : 10, qty : 100 }], [{ offerer : '$offerer', bidder : 'pradeep', symbol : 'ABC', price : 20, qty : 100 }], [{ offerer : '$offerer', bidder : 'taran', symbol : 'ABC', price : 20, qty : 200 }] ] var jsl = new JSL({ rules: bids, query: [offer] }); var response = jsl.run(); console.log('contract: ', response); module.exports = response; /* response [ [ { "offerer": "sandeep", "bidder": "pradeep", "symbol": "ABC", "price": 20, "qty": 100 } ] ] */ ``` The system produces a completed contract based on the one matching bid in the set of bids. <span id="transform"> </span> ## Data transformation We now introduce multiple matching bids, and ask the system to produce just a list of names of matching bidders. ``` var JSL = require('lib-jsl'); var offer = { offerer : 'sandeep', bidder : '$bidder', symbol : 'ABC', price : 20, qty : 100 } var bids = [ [{ offerer : '$offerer', bidder : 'kavi', symbol : 'ABC', price : 10, qty : 100 }], [{ offerer : '$offerer', bidder : 'pradeep', symbol : 'ABC', price : 20, qty : 100 }], [{ offerer : '$offerer', bidder : 'taran', symbol : 'ABC', price : 20, qty : 200 }], [{ offerer : '$offerer', bidder : 'naveen', symbol : 'ABC', price : 20, qty : 100 }], [{ offerer : '$offerer', bidder : 'prashant', symbol : 'ABC', price : 25, qty : 200 }] ] var jsl = new JSL({ rules: bids, query: [offer], transform: '$bidder' }); var response = jsl.run(); console.log('matching bidders ', response); module.exports = response; /* response [ "pradeep", "naveen" ] */ ``` We used the query variable '$bidder' to transform (shape) the result. By indicating `transform : '$bidder'` we asked the system to produce an array of values which were assigned to the variable '$bidder'. The transform can be an arbitrary JS object containing any of the variables from the query. _Note: If the transform is left unspecified, the result is an array of arrays, i.e. a valid JSL batch, with each element of the outer array becoming a fully instantiated version of the query object._ <span id="rules"> </span> ## Logic programming / Rules We complete our basic example by introducing multiple bids as well as offers, and ask the system to produce a set of possible matches (contracts). ``` var JSL = require('lib-jsl'); var offers = [ [{ type : 'offer', offerer : 'sandeep', bidder : '$bidder', symbol : 'ABC', price : 20, qty : 100 }], [{ type : 'offer', offerer : 'shekhar', bidder : '$bidder', symbol : 'ABC', price : 25, qty : 100 }], [{ type : 'offer', offerer : 'ruchir', bidder : '$bidder', symbol : 'ABC', price : 20, qty : 200 }], [{ type : 'offer', offerer : 'prachi', bidder : '$bidder', symbol : 'ABC', price : 25, qty : 200 }] ] var bids = [ [{ type : 'bid', offerer : '$offerer', bidder : 'kavi', symbol : 'ABC', price : 10, qty : 100 }], [{ type : 'bid', offerer : '$offerer', bidder : 'pradeep', symbol : 'ABC', price : 20, qty : 100 }], [{ type : 'bid', offerer : '$offerer', bidder : 'taran', symbol : 'ABC', price : 20, qty : 200 }], [{ type : 'bid', offerer : '$offerer', bidder : 'naveen', symbol : 'ABC', price : 20, qty : 100 }], [{ type : 'bid', offerer : '$offerer', bidder : 'prashant', symbol : 'ABC', price : 25, qty : 200 }] ] var rules = [ [ //head { match : {offerer : '$offerer', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty'}}, //body { type: 'bid', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty'}, { type : 'offer', offerer : '$offerer', symbol : '$symbol', price : '$price', qty : '$qty'}, ] ]; var jsl = new JSL({ rules: rules.concat(bids, offers), query: [{match: '$match'}], transform: '$match' }); var response = jsl.run(); console.log('contracts: ', response); module.exports = response; /* response [ { "offerer": "sandeep", "bidder": "pradeep", "symbol": "ABC", "price": 20, "qty": 100 }, { "offerer": "ruchir", "bidder": "taran", "symbol": "ABC", "price": 20, "qty": 200 }, { "offerer": "sandeep", "bidder": "naveen", "symbol": "ABC", "price": 20, "qty": 100 }, { "offerer": "prachi", "bidder": "prashant", "symbol": "ABC", "price": 25, "qty": 200 } ] */ ``` The rules array now contains a full JSL batch comprising of a rule as well as facts (data). Bids and offers have been given a type attribute which identifies them. ### rules The first object in the matching rule is the _head_, and the remaining objects, are the _body_. The rule specifies that it is looking for a combination of bid and offer records where '$symbol', '$price', and '$qty' are the same. The '$bidder' and '$offerer' are extracted from the appropriate type of record to construct the final output in the head of the rule. [ //head { match : {bidder : '$bidder', offerer : '$offerer', symbol : '$symbol', price : '$price', qty : '$qty'}}, //body { type: 'bid', bidder : '$bidder', symbol : '$symbol', price : '$price', qty : '$qty'}, { type : 'offer', offerer : '$offerer', symbol : '$symbol', price : '$price', qty : '$qty'}, ] ### facts The bids and offers are both an array of arrays containing a single object each; Each bid and offer is a _fact_ : it has a _head_ but no _body_. // head only [{ type : 'bid', bidder : '$bidder', offerer : 'prashant', symbol : 'ABC', price : 25, qty : 200 }] ### query The query can also be seen as a rule : query: [{match: '$match'}] Finally, note that we concatenate rules and facts (data) before calling Jsl, combining all rules and facts into one array. <span id="algorithm"> </span> ## JSL algorithm For any given rule, the _head_ is _satisfied_ if each part of the _body_ is _satisfied_. Thus _facts_ are always _satisfied_. Each part of the _body_ is _unified_ against the set of rules, to find a rule where the _head_ of the rule _matches_ the body part. Since we are dealing with JS objects, we define _match_ to mean containment, i.e. the body part must be fully contained in the head of the _matched_ rule. In this example, the query object `{ match: ... }` is completely contained in the head of the matching rule as a path (only the keys/paths matter for _matching_). Query execution proceeds by attempting to _satisfy_ the query object by _unifying_ it against the rules. If any rule _matches_ the query, the unification proceeds recursively into its _body_ parts, until there are no more _body_ parts to be _satisfied_ , or a _body_ part fails to _unify_. All rules that _match_ the query are tried. Thus a query can produce multiple results. In this example, the query _matches_ the matching rule, and each _body_ part of the matching rule successfully unifies against pairs of bid and offer records; so we obtain a set of fully instantiated '$match' values in the result. <span id="summary"> </span> ## Summary This overview showed a naive bid/offer matching procedure which started by merging two objects, and progressed in complexity to produce matching pairs from multiple types of records. The [next chapter](http://kavi-saralweb.github.io/docs/html/features.html) introduces features of JSL (builtins, callbacks) which allow refinement of the matching procedure to make it more capable. We have found JSL useful for the following tasks : - Data binding * As described in [this overview](http://kavi-saralweb.github.io/docs/html/overview.html), data binding involves filling out "holes" in a JS object using data supplied as another set of JS objects. Bids and Offers are merely unbound objects waiting to be bound to a "matching" object. - Information Retrieval * Maintain and query small, in-memory databases of non trivial complexity such as configurations, dependencies, etc. See [information retrieval example](http://kavi-saralweb.github.io/docs/html/example-ir.html). The chapter is based on a textbook example, but covers salient concepts of information retrieval. - Test automation * Specification of expected output from a JSON returning api, see [test automation example ](http://kavi-saralweb.github.io/docs/html/example-ta.html) We expect JSL to find more applications over time as an embedded logic-programming library for JS/JSON.