Last active
August 29, 2015 14:10
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"]]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.