Open kidpollo opened 3 years ago
I haven't tried it with more input data than the first example, but it might be doable with less macro:
(defmacro ->fn
([fn-name body]
`(fn ~fn-name [] ~body))
([fn-name arg body]
`(fn ~fn-name [~arg] ~body)))
(defn parsed->rule
[{:keys [rule-name fn-name conditions when-body then-body then-finally-body arg]}]
(o/->Rule rule-name
(mapv o/map->Condition conditions)
(when (some? when-body) ;; need some? because it could be `false`
(->fn fn-name arg when-body))
(when then-body
(->fn fn-name arg (first then-body)))
(when then-finally-body
(->fn fn-name (first then-finally-body)))))
(defn ->rule
[rule-key rule]
(parsed->rule (o/->rule [rule-key (o/parse ::o/rule rule)])))
(defn ->ruleset
"Returns a vector of rules after transforming the given map."
[rules]
(->> (map o/->rule
(o/parse ::o/rules rules))
(mapv parsed->rule)))
@rwstauner did not work with my example data. I like its less macroey but need to debut why it did not work for me
i get
[{:id "1", :x 3, :y 1}]
[{:id "1", :x 3, :y 1}]
the first code was limiting then bodies to 1 which i didn't realize was incorrect. in revising i'm struggling with nested expansion, but it seems like it can be done with eval and no macros:
(defn parsed->rule
[{:keys [rule-name fn-name conditions when-body then-body then-finally-body arg]}]
(o/->Rule rule-name
(mapv o/map->Condition conditions)
(when (some? when-body) ;; need some? because it could be `false`
(eval `(fn ~fn-name [~arg] ~when-body)))
(when then-body
(eval `(fn ~fn-name [~arg] ~@then-body)))
(when then-finally-body
(eval `(fn ~fn-name [] ~@then-finally-body)))))
(defn ->rule
[rule-key rule]
(parsed->rule (o/->rule [rule-key (o/parse ::o/rule rule)])))
(defn ->ruleset
"Returns a vector of rules after transforming the given map."
[rules]
(->> (map o/->rule
(o/parse ::o/rules rules))
(mapv parsed->rule)))
Really neat. I think @rwstauner's solution is good, but you don't actually even need eval, if you're willing to give up some syntactic convenience. The macro or eval is really just saving you from having to explicitly make fn
s with the correct arguments and destructuring the bindings explicitly. Now that i think of it, i really should provide support for this approach in the library itself. I'll look into it when i have time. Here it is without macros or eval.
(require '[odoyle.rules :as o])
(defn parsed->rule
[{:keys [rule-name fn-name conditions when-body then-body then-finally-body arg]}]
(o/->Rule rule-name
(mapv o/map->Condition conditions)
when-body
(first then-body)
(first then-finally-body)))
(defn ->rule
[rule-key rule]
(parsed->rule (o/->rule [rule-key (o/parse ::o/rule rule)])))
(defn ->ruleset
"Returns a vector of rules after transforming the given map."
[rules]
(->> (map o/->rule
(o/parse ::o/rules rules))
(mapv parsed->rule)))
(def generated-rule
[:what
'[id :player/x x]
'[id :player/y y]
:when
(fn [{:keys [x y] :as match}]
(and (pos? x) (pos? y)))
:then
(fn [{:keys [id] :as match}]
(o/insert! id :session/player match))
:then-finally
(fn []
(println "Query from inside: " (o/query-all o/*session* :session/player)))])
(println "Query from outside: "
(-> (o/->session)
(o/add-rule (->rule :session/player generated-rule))
(o/insert 1 {:player/x 3 :player/y 1})
(o/insert 2 {:player/x 5 :player/y 2})
(o/insert 3 {:player/x 7 :player/y -1})
o/fire-rules
(o/query-all :session/player)))
; => Query from inside: [{:id 1, :x 3, :y 1} {:id 2, :x 5, :y 2}]
; => Query from outside: [{:id 1, :x 3, :y 1} {:id 2, :x 5, :y 2}]
I just added this if you want to give it a shot. It is a new arity of odoyle.rules/->rule
so the code above is now just this:
(require '[odoyle.rules :as o])
(def generated-rule
[:what
'[id :player/x x]
'[id :player/y y]
:when
(fn [{:keys [x y] :as match}]
(and (pos? x) (pos? y)))
:then
(fn [{:keys [id] :as match}]
(o/insert! id :session/player match))
:then-finally
(fn []
(println "Query from inside: " (o/query-all o/*session* :session/player)))])
(println "Query from outside: "
(-> (o/->session)
(o/add-rule (o/->rule :session/player generated-rule))
(o/insert 1 {:player/x 3 :player/y 1})
(o/insert 2 {:player/x 5 :player/y 2})
(o/insert 3 {:player/x 7 :player/y -1})
o/fire-rules
(o/query-all :session/player)))
Thanks Zach! I’ll check it out later today! We are gearing up to using this lib :) we are stoked. Thanks again
On Tue, Jun 1, 2021 at 7:48 AM Zach Oakes @.***> wrote:
I just added this if you want to give it a shot. It is a new arity of odoyle.rules/->rule so the code above is now just this:
(require '[odoyle.rules :as o])
(def generated-rule [:what '[id :player/x x] '[id :player/y y] :when (fn [{:keys [x y] :as match}] (and (pos? x) (pos? y))) :then (fn [{:keys [id] :as match}] (o/insert! id :session/player match)) :then-finally (fn [] (println "Query from inside: " (o/query-all o/session :session/player)))])
(println "Query from outside: " (-> (o/->session) (o/add-rule (o/->rule :session/player generated-rule))
(o/insert 1 {:player/x 3 :player/y 1}) (o/insert 2 {:player/x 5 :player/y 2}) (o/insert 3 {:player/x 7 :player/y -1}) o/fire-rules (o/query-all :session/player)))
—
You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/oakes/odoyle-rules/issues/12#issuecomment-852187713, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAB75WOUJIOGDICDO4GRBLTQTXNPANCNFSM45JXYY6Q .
I ended up cutting a new release because why not :D
I also wrote a new section in the readme about it: Defining rules dynamically
Very nice !
On Tue, Jun 1, 2021 at 11:23 AM Zach Oakes @.***> wrote:
I ended up cutting a new release because why not :D
I also wrote a new section in the readme about it: Defining rules dynamically https://github.com/oakes/odoyle-rules#defining-rules-dynamically
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/oakes/odoyle-rules/issues/12#issuecomment-852347428, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAB75QDV3NQJFOR4FANBELTQUQQ3ANCNFSM45JXYY6Q .
I am very excited to see this :) I've wanted to use odoyle rules in conjunction with home assistant to power my smart home.
I had a basic prototype working but the inability to dynamically create rules made it difficult. Now I can dust that off and give it a go again.
I've been evaluating this awesome project great work!
For my usecase I have to dynamically create rulesets. I think I have implementation for this but my macro fu is not the best I dont love I had to use
eval
. Is this something that you would like to make part of this project? I can submit a pr if so.