The Kynetx Rule Language has Modules


Apollo 13 Command Module

KRL (Kynetx Rule Language) started out as a fairly small domain specific language for augmenting Web pages. We integrated with several online services (like geolocation, weather, and so on) to make what we were doing simpler to write. Later, as KRL grew, we added more integrations with Web APIs and services like Twitter. We've increased the abstraction capabilities of the language by adding first-class functions.

Lately we've realized that many of the native integrations of external APIs we've done could--and should--be done by developers working in KRL. If only they had a way to encapsulate and share them! Now they do. This week we pushed code that added modules to KRL.

In KRL, any ruleset can be a module. At present, only declarations in the global block are available to the ruleset that uses the module. There are language changes in the works that will allow actions to defined and shared in a module as well. Please stand by, as they say. Not everything in the global block has to be exported, so you can keep things private. Modules can be parametrized and aliased. For details see the module documentation.

Alex Olsen created a nice little demo of modules that I'll share to show how they work. One of the little tools we use to make storing temporary information easier is called StringBin. StringBin allows you to create key-value pairs in the cloud and has a simple API. Alex defined his StringBin module like so (Gist here):

ruleset a369x115 {
  meta {
    name "StringBin Module"
    description <<
      StringBin Module
    >>
    author "AKO"
    logging off

    provide read, write, destroy
    configure using pin = "nopin"
  }

  global {
    datasource write <- "http://api.stringbin.com/1/write?" 
               cachable for 1 second
    datasource read <- "http://api.stringbin.com/1/read?" 
               cachable for 1 second

    write = function(k,v) {
      datasource:write({"pin":pin,"key":k,"value":v})
    }

    read = function(k) {
      datasource:read({"pin":pin,"key":k})
    }

    destroy = function(k) {
      datasource:write({"pin":pin,"key":k,"value":""})
    }
  }
}

The important things to note here are the provide and configure clauses. The provide keyword is followed by a list of names that will be available outside the module. The configure keyword indicates the module parameters and their default values. Alex's module has one parameter, the StringBin pin or developer token and it defaults to "nopin". Note that that datasource declarations are not provided to rulesets using the module and the implementation details are effectively hidden.

You can use this module like so:

meta {
  use module a369x115 alias StringBin 
         with pin = "X9ooUUsrR180MkpxZ2N1M"
}
...

rule write_to_stringbin {
  select when pageview ".*"
  pre {
    stuff = StringBin:write("yellow", "mellow");
  }
...
}

Note: I've cut out large pieces of Alex's example, for the complete sample ruleset that uses his module, see the Gist.

In the preceding code, the module is used by putting a use module clause in the meta block. The use module clause needs a ruleset name and optionally an alias (StringBin in this case) and configuration (setting the pin in this case). Later, the write function from the StringBin module is used to write the value "mellow" to the key "yellow".

Because modules are parametrized and can be aliased, you can use the module multiple times with different parameters. For example, suppose we had two StringBins and we wanted to copy a value out of one into the other. We could accomplish that like so:

meta {
  use module a369x115 alias BinA 
         with pin = "X9ooUUsrR180MkpxZ2N1M"
  use module a369x115 alias BinB 
         with pin = "ada98u0ada0kkdad0a0da"
}
...

rule copy_stringbin {
  select when pageview ".*"
  pre {
    stuffToCopy = BinA:read("yellow");
    results = BinB:write("yellow", stuffToCopy);
  }
...
}

In the preceding code, we've attached to two bins called BinA and BinB and we're reading something out of one and writing to the other.

Parametrized modules provide a convenient way to encapsulate complex behavior and use it over and over again. Now that you don't need language mods to make your API calls all pretty, I'm expecting to see an explosion of APIs that are integrated--via modules--into KRL.