Too Much Code

Not enough cohesion

Rules as Data

This started as a topic for a Lambda Lounge meetup but seems worth sharing broadly.
I’ve posted previously about treating rules as a control structure, but for Clara 0.4 rules are now first-class data structures with well-defined schema.  The simple defrule syntax is preserved but the macro now produces a data structure that is used everywhere else. For example, the following code:
1
2
3
4
5
(defrule date-too-early
  "We can't schedule something too early."
  [WorkOrder (< (weeks-until date) 2)]
  =>
  (insert! (->ApprovalRequired :timeline "Date is too early")))
Defines a var containing this structure:
1
2
3
4
5
6
7
8
{:doc "We can't schedule something too early.",
 :name "date-too-early",
 :lhs
 [{:constraints [(< (llkc.example/weeks-until date) 2)],
   :type llkc.example.WorkOrder}],
 :rhs
 (clara.rules/insert!
  (llkc.example/->ApprovalRequired :timeline "Date is too early"))}
The rule structure itself is defined in Clara’s schema, and simply stored in the var with the rule’s name.

So, now that rules are data, we open the door to tooling to explore and manipulate them. For instance, we can visualize the relationship between rules. Here is a visualization of the rules used for the meetup. I arbitrarily chose shapes to distinguish the different types of data:



This image is generated by running the following function from clara.tools.viz against the example used at the meetup:
1
(viz/show-logic! 'llkc.example)
That function simply scans each rule in the given namespace, reads individual conditions from the rule data structure, and wires them up. The graph itself is rendered with GraphViz.

Since all rules of data, they can be filtered or manipulated like any Clojure data structure. So here we take our set of rules and only display those that handle logic for validation errors:
1
2
3
(viz/show-logic!
  (filter #(viz/inserts? % ValidationError)
    (viz/get-productions ['llkc.example])))
In this example there is only one rule that does validation, so the resulting image looks like this:

Rule engine as an API

The rules-as-data model creates another significant advantage: we have decoupled the DSL-style syntax from the rule engine itself. Clara users can now create rules from arbitrary sources, such as a specialized syntax, an external database, or domain-specific file format. (Think instaparse tied to rule generation.) Clara is evolving into a general Rete engine, with its “defrule” syntax being just one way to use it.

So far I’ve written only simple, GraphViz-based visualizations but we can expose these with more sophisticated UIs. Projecting such visualizations onto, say, a D3-based graph could provide a rich, interactive way of exploring logic.

At this point, Clara 0.4 is available as snapshot builds. I expect to do a release in February, pending some cleanup and enhancements to ClojureScript support. I’ll post updates on my twitter feed, @ryanbrush.