Last active
August 29, 2015 14:10
-
-
Save lantiga/b65dd7a1d7c3bb35a7b1 to your computer and use it in GitHub Desktop.
Naive pure rule engine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns carla.core | |
(:require [clojure.math.combinatorics :as combo])) | |
(defn- fact-matches? [fact match] | |
(= match (select-keys fact (keys match)))) | |
(defn make-rules [] []) | |
(defn make-session [] {:facts #{}}) | |
(defn add-rule [rules match-map & conds-and-body] | |
(let [conds (butlast conds-and-body) | |
body (last conds-and-body) | |
match-keys (keys match-map) | |
match-vals (vals match-map) | |
rule (fn [session] | |
(->> match-vals | |
(map (fn [match] (filter #(fact-matches? % match) (:facts session)))) | |
(apply combo/cartesian-product) | |
(map #(zipmap match-keys %)) | |
(filter (fn [matched] (every? (fn [cnd] (cnd session matched)) conds))) | |
(reduce body session)))] | |
(conj rules rule))) | |
(defn insert-fact [session fact] | |
(update-in session [:facts] conj fact)) | |
(defn retract-fact [session fact] | |
(update-in session [:facts] disj fact)) | |
(defn match-facts [session match] | |
(filter #(fact-matches? % match) (:facts session))) | |
(defn fire-rules [session rules] | |
(reduce (fn [s rule] (rule s)) session rules)) | |
(defn fire-rules* [session rules max-iter] | |
(loop [session session | |
c 0] | |
(let [new-session (fire-rules session rules)] | |
(if (or (= new-session session) (= c max-iter)) | |
session | |
(recur new-session (inc c)))))) | |
(defn doit [] | |
(let [rules (-> (make-rules) | |
(add-rule | |
{:tick {:type :Tick} | |
:init {:type :Initialized}} | |
(fn [s {{stamp :stamp} :tick {timestamp :timestamp} :init}] | |
(= stamp timestamp)) | |
(fn [s {{tick-boo :boo} :tick {init-boo :boo} :init}] | |
(and tick-boo init-boo)) | |
(fn [s {{stamp :stamp boo :boo} :tick}] | |
(insert-fact s {:type :Tock :stamp stamp :boo boo}))))] | |
(-> | |
(make-session) | |
(insert-fact {:type :Tick :stamp 100 :boo false}) | |
(insert-fact {:type :Tick :stamp 100 :boo true}) | |
(insert-fact {:type :Tick :stamp 200 :boo true}) | |
(insert-fact {:type :Initialized :timestamp 100 :boo true}) | |
(insert-fact {:type :Initialized :timestamp 300 :boo false}) | |
(fire-rules* rules 10) | |
(match-facts {:type :Tock})))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defproject carla "0.1.0-SNAPSHOT" | |
:description "FIXME: write description" | |
:url "http://example.com/FIXME" | |
:license {:name "Eclipse Public License" | |
:url "http://www.eclipse.org/legal/epl-v10.html"} | |
:dependencies [[org.clojure/clojure "1.6.0"] | |
[org.clojure/math.combinatorics "0.0.8"]]) |
I’m surely missing something: why do you need to reduce
over session twice? Once in fire-rules
and then in the rule body?
Also, I seem to remember that reduce
is not lazy (in 1.6 at least)? OK, this is a naive rule engine but if you plan to build upon this experiment it could worth considering the implications.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Accumulators (i.e. pre-filtering or accumulating individual matches before conditions are fired) could be added as