1.0.0
After over two years, Adam Sandler's favorite Clojure rules engine has reached 1.0. I decided to bump the version to this important milestone because the previous version was 0.12.0 and 13 is an unlucky number. I call it superstitious versioning, or SupVer for short.
Here's why O'Doyle rules even more:
Improved debugging with wrap-rule
There is a new function to help with debugging called wrap-rule. You can use it on some or all of your rules to intercept the various places where things execute in the engine. In the simplest case, this is just a convenient way to add logging, but like any simple tool you are free to use it in any creative way you wish. See the new debugging section for more.
The *session*
and *match*
dynamic vars are no longer necessary
This was a longstanding design mistake I always wanted to correct. The *session*
and *match*
vars never really needed to exist, and now you can just reference session
and match
in your rules directly (unless you're using those as binding symbols in your :what
block!). This may be a modest performance improvement, but mainly it is just better style. If you can pass values as explicit args instead of dynamic vars, you should do so.
The dynamic vars remain for backwards compatibility. There is only one thing that required a breaking change: Defining rules dynamically with ->rule
. Previously, the syntax looked like this:
;; notice it's a vector, and the functions do not
;; receive an explicit `session`
(o/->rule
::character
[:what
'[id ::x x]
'[id ::y y]
:when
(fn [{:keys [x y] :as match}]
(and (pos? x) (pos? y)))
:then
(fn [match]
(println "This will fire twice"))
:then-finally
(fn []
(println "This will fire once"))])
Now it looks like this:
;; notice it's a map, and the functions *do*
;; receive an explicit `session`
(o/->rule
::character
{:what
'[[id ::x x]
[id ::y y]]
:when
(fn [session {:keys [x y] :as match}]
(and (pos? x) (pos? y)))
:then
(fn [session match]
(println "This will fire twice"))
:then-finally
(fn [session]
(println "This will fire once"))})
Changing it from a vector to a map serves a few purposes. Firstly, it just makes more sense as a map. Secondly, it allows me to throw a nice error if you try to make a rule with the old syntax, instead of just throwing a mysterious arity exception when the functions are eventually run.
Error to prevent a {:then not=}
footgun
The ability to pass arbitrary functions to a tuple's :then
option is really powerful. That said, there was a footgun in the library that was easy to run into that led to confusing behavior. Initially, I just added a warning in the README, but as of this version, it correctly detects the problem and throws a helpful error message guiding you on how to fix it. Fail fast, just like the O'Doyle family: