precept 2017-06-09

Looks like we might want to support that...

You'd like to have for insertions correct?

That's how I was using it. Not sure if it's applicable elsewhere.

Have you guys gotten this to work in a "reloadable" fashion? Right now I need to refresh my whole page before the rules are redefined.

Sorry for the bombardment of questions šŸ˜ I really think you guys have hit the nail on the head here for UI development.

@kenny we are loving it, keep it coming šŸ™‚

You have no idea how helpful it is for us šŸ™‚

BTW, I have added a note in issue #28 to make sure we support nested maps (per Kenny's example)

You’re not alone with having to refresh the page. I believe this is the case with Clara and think it has to do with their CLJS implementation

I’m really not sure though. It seems like the reloading should work even though we’re dealing with macros

Yeah, it certainly seems like it should just work given it's just interning a var.

I'll open an issue so it can be kept in mind. Certainly worth investigating.

Oh thanks I was just about to šŸ™‚

Good issue

Is there anyway to see a list of all facts currently in the session? I see Clara has clara.tools.inspect but that isn't available in CLJS.

Thanks for opening that

Well, one way is to write a print all facts rule.

(rule print-all-facts
  [?fact <- [_ :all]]
  =>
 (println "Fact!" ?fact)

You can see the viewmodel/store state by derefing precept.state/store. They won’t be in tuple format and but they should be what you have in the session at the end of every fire-rules

It’s also what your components ultimately get

Ah yeah, that is useful.

Which one? šŸ™‚

I just wanted to inspect the results of my rules after they had been ran.

It'd be cool to have a tool similar to re-frisk to visualize the current and past state.

Is refrisk more tracing though? We were thinking something more similar to redux devtools

It is but I was thinking smaller steps šŸ™‚ Making your own redux devtools is quite ambitious šŸ™‚

Hm. Ok. I hear you

We could easily print out ā€œRetracted! xā€ ā€œAdded! yā€

As a first step, I think that'd be handy.

Also, not sure if you guys have a personal or company dev blog but posts about Precept would be great, especially if they were interactive with Klipse.

Of course that depends on how much time you guys have.. šŸ˜‰

Sure. Tutorial type posts with examples is what you'd like to see?

šŸ’Æ 1

alex-dixon: App structure, talking to servers, debugging, testing

šŸ‘ 1

@quan has joined the channel

set the channel topic: Preception!

@alex-dixon You mentioned yesterday that you were thinking about rules without names, but it occurred to me this morning that might be difficult to resolve with dynamic loaders like Figwheel.

@weavejester Correct. We’re already facing an issue similar to that it seems. Page must be refreshed for rule changes to take effect https://github.com/CoNarrative/precept/issues/80

You might notice define does not accept a name so we have already crossed this precipice for better or worse

@alex-dixon Couldn’t you give defines names as well?

Yes though I’m fairly confident the issue with having to refresh the page predated define having an autogenerated name

I guess if a rule is redefined, the associated session still has the old rules.

Yes. Was just going to mention if you change the name of a rule you now have two rules

Any idea why a rule would return true after a fact has been retracted?

Given what I just told weavejester I guess make sure you didn’t rename a rule šŸ™‚

Is there another fact that could be satisfying the condition?

The fact simply isn't there. I am checking by printing the store.

Ok. Would you be able to copy in the rule in question?

Sure. Lemme make an example. Still messing around with the example login form I sent yesterday.

So the logging-in rule is being called every time the email or password inputs change.

Ok. Taking a look

Just added the reagent component if that helps as well..

Perfect. Was going to ask šŸ™‚

Ignore the duplicate rule.. šŸ˜†

šŸ‘ 1

Hm….well. You might want :logging-in? to be :transient

So I never explicitly set :logging-in? to false but I do retract it.

Not sure that will solve the problem

Well, the problem is do-login is async.

It technically should be :transient but all of the rules would run before that request completes.

I assumed it's not recommended to have a long-running synchronous function call in the RHS.

I wouldn’t necessarily say that. I think it depends. Sometimes that’s what you want. Maybe that’s the case here?

Other things could be happening in the UI while that request is going through. Wouldn't that block the rules from continually running?

This is true šŸ™‚

When do you see the retraction happening

When it should -- after inserting :login-response.

But then everytime :email or :password changes from that point on, the logging-in rule is ran.

It's almost like it's stuck in a cache or something.

And I’m guessing :logging-in isn’t marked as :one-to-many in a schema

(rules/session
  app-session
  'precept-play.rules)

Well, I’m having a hard time seeing how ā€œlogging-inā€ is sticking around. I’d suggest adding a rule that says ā€œif I’m logged in I’m not logging inā€

I realize you have this with the response but I’d try adding an additional rule for :logged-in?

Sorry, new rule or change to existing rule?

I’d add a new rule for it

Added this:

(rules/rule not-logging-in-when-logged-in
  [[_ :logged-in? true]]
  [[_ :logging-in? true]]
  =>
  (precept.util/retract! [:global :logging-in? true]))
And still getting the same problem.

Ok. Lightbulb

[?fact <- [_ :logging-in? true]]

No slack clj highlighting 😢

For the login response rule add that line

Yeah heh always forget

But yeah try adding that line and retracting ?fact instead of the vector version of it

Ok so why does my version not work?

Well, it should first off. Sorry for your troubles šŸ™‚

ā€œUnder the coversā€ all facts are Tuple records that have e a v fact-id, where fact-id is incremented for each fact added to the session

So when retracting I think we do the same thing as inserting. If you supply a vector, we add that incremented fact id for you. So that gets passed to Clara’s retract, which happily retracts a fact that doesn’t exist

The fact that does exist is the one in the session with that particular fact id, so you have to match on the whole thing in order to retract it

So using <- binds the whole tuple from the right hand side to the left?

Yes. This is one of the ways our syntax is like Clara’s

Got it. Some more feedback for you guys.. I think an API should limit the number of ways you can do one thing as best as possible. So, I think retract!, insert!, etc. should all take a vector of facts rather than a single fact or a vector of facts.

Ok. So for inserting a single fact you’d prefer (insert! [[id :bar "baz"]])?

Most definitely. It's slightly more verbose but far less obtuse.

As an example, Datomic's transact only takes a list of tx-data, never a single datom/map.

Good example šŸ™‚

I’ve made a note. I made the decision based off my own laziness in response to Clara having insert!, insert-all!, insert-unconditional!, insert-all-unconditional!, insert and insert-all

IMO it's much better to just have one function that takes exactly one type of argument.

Yeah, that list of fns is nasty. My intuition tells me that the author did that for some sort of performance reasons. Not sure if that is true or not.. šŸ™‚

I think you’re right. Thanks for the feedback that makes a lot of sense to me

Also I just came across your spectomic library. Thanks for making that, speaking of something that’s been bugging me šŸ™‚

I'm surprised you found that actually

I think me and everyone else have been looking for it for some time now šŸ˜„

Haha maybe. It wasn't clear to me if that was the right solution. I wanted to give it some time internally to see if it actually shakes out well.

I’ve heard it mentioned more than once that people would like the two to converge or be compatible in some way

Oh ok. I was going to ask if you’d mind if I tweeted about it šŸ™‚

My main concern was the inherent coupling you make between app level specs and Database schema. Maybe that's the way things typically shake out, maybe not ĀÆ\(惄)/ĀÆ

But yeah you can šŸ™‚

What is your Twitter name?

I’m very creative šŸ™‚

Anyway, why are rules named? Is it only for documentation purposes?

Was talking to weavejester about this earlier. We’d prefer them not to be but managing that is a little tricky and I ran out of time

Ah, yes I see that. Yeah, that definitely seems like something that should be figured out. Consistency is important šŸ™‚

Yeah. define seems to be working. There are some larger issues that we’d like to solve there like not multiplying rules when you rename them and not having to refresh the page for a change to a rule to take effect

I think you opened the issue for that yesterday

Though, I think a name and/or a docstring would be important to have as well

Would you be ok with no names but allow docstrings? For all our macros

I think if there’s no names, there should be a way of grouping rules, perhaps.

It’s a shame that Clara Rules doesn’t have anonymous rules. The code makes it look like that’s certainly possible.

(def my-session
  (precept/session
   (precept/rule [...] => [...])))

That would be a very pretty API

Or maybe:

(def my-session
  (precept/session
   ([...] => [...])))

Is there a reason you suggest embedding rule inside session? I'd think that having a defrules macro might be cleaner. Then you'd pass that symbol created by defrules to session's opts.

You could also have grouping with defrules.

My understanding is that you have rules, and groupings of rules called sessions.

Is there any reason rules couldn't be reused across sessions?

They could, but then you’d write:

(def my-rule
  (precept/rule [...] => [...]))

(def my-session
  (precept/session my-rule ...))

If rules are first-class you could just assign a var to them.

And the linking between sessions and rules would use Clojure’s normal require mechanism

So it should be reload-safe.

LGTM. Could have defrule as a shortcut as well.

Right šŸ™‚

But Clara Rules has some odd complexity that seems unnecessary

(defmacro defrule
  [name & body]
  (let [doc (if (string? (first body)) (first body) nil)
        body (if doc (rest body) body)
        properties (if (map? (first body)) (first body) nil)
        definition (if properties (rest body) body)
        {:keys [lhs rhs]} (dsl/split-lhs-rhs definition)

        production (cond-> (dsl/parse-rule* lhs rhs properties {})
                           name (assoc :name (str (clojure.core/name (com/cljs-ns)) "/" (clojure.core/name name)))
                           doc (assoc :doc doc))]
    (add-production name production)
    `(def ~name
       ~production)))

The def part in the macro could just be removed

I think šŸ™‚

I think there’s dsl/parse-rule that does that

As far as I can tell, the extra stuff in defrule is to set the docstrings, etc, and to add all the rules to a place where they can be loaded per namespace

And loading per namespace is convenient, but…

Kinda makes reloading hard.

I think mk-session accepts a list of ā€œproductionsā€ that may be rules http://www.clara-rules.org/apidocs/0.15.0/clojure/clara.rules.html#var-mk-session

Like dsl/parse-rule it’s CLJ only

Hm. It looks like the rules are only available at compile-time to the Clojure compiler.

;; Store production in cljs.env/*compiler* under ::productions seq?
(defn- add-production [name production]
  (swap! env/*compiler* assoc-in [::productions (com/cljs-ns) name] production))

(defn- get-productions-from-namespace
  "Returns a map of names to productions in the given namespace."
  [namespace]
  ;; TODO: remove need for ugly eval by changing our quoting strategy.
  (let [productions (get-in <@U095WMJNR>/*compiler* [::productions namespace])]
    (map eval (vals productions))))

(defn- get-productions
  "Return the productions from the source"
  [source]
  (cond
   (symbol? source) (get-productions-from-namespace source)
   (coll? source) (seq source)
   :else (throw (IllegalArgumentException. "Unknown source value type passed to defsession"))))

They’re also doing this for CLJS I think

This is the top of clara/macros.clj

Yeah, so the rules are defined and assembled in Clojure… I think

I think Clojurescript’s eval has matured to the point where that might not be necessary

Including ClojureScript’s eval adds a whole lot of overhead to the compiled JS

Like the entire compiler?

Because it effectively needs to include all of the ClojureScript compiler.

Otherwise Closure just excludes all that.

It looks like Clara Rules operates a little like core.match, in that you have a bunch of rules that are evaluated at compile time into Clojure code.

@alex-dixon Pretty sure any eval in your ClojureScript means you need all the compiler stuff.

I guess it would be easy to check

I’ve found Mike Fikes to be an expert in this area

I can’t tell if he’s saying Planck can eval Clojurescript in Javascript http://blog.fikesfarm.com/posts/2016-01-22-clojurescript-eval.html

I’m just checking the sizes…

79K without, 1.1M with. Assuming I’ve done it right.

Thanks for doing that

For reference, I compared:

(ns cljseval-test.core
  (:require [cljs.js :refer [empty-state eval]]))

(eval (empty-state) '(println "x") identity)

To:

(ns cljseval-test.core)

(println "x")

With eval it was 1.1M, without 79K.

😢

I think I get what Clara Rules is doing now.

Without its custom registry, you’d have to define all the rules together, the same as core.match. Perhaps the authors wanted to leverage namespaces.

I have a few thoughts on the rules setup

I’m not sure how good these will be. They might be half-baked šŸ™‚

So my understanding of the ClojureScript implementation of Clara Rules is that the rules themselves barely touch ClojureScript. They’re defined in Clojure, shoved into the compiler environment, then consumed by the session. They don’t really exist inside of ClojureScript per se.

Because they don’t really map to vars, I wonder if it’s worth considering a different registry. When clojure.spec was created, the decision was made not to overload vars and namespaces, and to create a separate registry for specs.

As compile-time artifacts, I wonder if it’s worth looking at rules in the same light.

So:

(p/defrule ::todo-is-visible
  [[_ :visibility-filter :active]]
  [[?e :todo/done false]]
  =>
  (p/insert! [?e :todo/visible true]))

Using a keyword as the identifier over a symbol would indicate that the rules are not vars in the usual sense. Behind the scenes, perhaps it maps out to something like…

(swap! env/*compiler* assoc-in [::registry ::todo-is-visible]
      [:rule '([[_ :visibility-filter :active]]
               [[?e :todo/done false]]
               =>
               (p/insert! [?e :todo/visible true])])])

So just take the data verbatim and stuff it in a registry. Essentially keep all the rules as data until they’re passed into the session and constructed into an opaque block of code.

Internally Clara Rules might do this - not sure - but Precept adds some additional stuff like subs, so it might be worth keeping everything as compile-time data.

Alternatively:

(swap! env/*compiler* assoc-in [::registry :rules ::todo-is-visible]
      '([[_ :visibility-filter :active]]
        [[?e :todo/done false]]
        =>
        (p/insert! [?e :todo/visible true])])])

When creating the session we should be able to do it anonymously…

šŸ‘ 1

From my reading of clara.macros anyway.

So maybe something like:

(def session
  (p/session :rules [:*] :subs [:example.subs/*]))

Could use some kind of basic pattern matching to restrict the rules used in the session.

:* - any rule, maybe the default

:namespace/* - any rule in a namespace

:namespace/name - specific rule

I think it might still be worth identifying rules somehow, because it both documents and provides a degree of uniqueness.

The general idea is to draw a clear dividing line between what’s set up at compile time (the rules registry), and what’s set up at runtime (the session).

This kind of registry has been successful for spec, and in a more similar context re-frame as well. It also seems like this can be implemented without changing clara, which goes some way towards making the rules engine more pluggable?

@carkh has joined the channel