Coder Social home page Coder Social logo

conarrative / precept Goto Github PK

View Code? Open in Web Editor NEW
653.0 653.0 33.0 1.29 MB

A declarative programming framework

License: MIT License

Clojure 91.47% Shell 5.47% HTML 0.40% CSS 2.66%
clojure clojurescript declarative-programming functional-reactive-programming functional-relational-programming logic-programming rules

precept's People

Contributors

abustamam avatar adamfrey avatar alex-dixon avatar kennyjwilli avatar martinklepsch avatar mikegai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

precept's Issues

Rename core functions/macros

Suggest the following:

then -> โœ…

  • Definitely took a chance on this one. I like it but I'm not convinced it's OK. Looking for feedback. Edit: Definitely appropriate. It inserts facts directly. Acts as/is then/consequence/rhs.

subscribe -> โœ…

  • Ok with keeping as is but there might be a more appropriatebetter term. Open to feedback.

deflogical -> define

  • Not using this in examples right now and should revisit its implementation. Adding it here because if we keep it we probably want cohesion with the rest of the macro names. Would seem to entail dropping def. Problematically logical doesn't seem to make a whole lot of sense without def in front.

def-tuple-rule -> rule

  • Won't conflict with Clara
  • Violates convention of using def in the name of a macro to indicate assignment to a Var. Not sure it's really that helpful to communicate this in the name of the macro. Should be clear it's not a function call to anyone using the lib without def in the macro name.

def-tuple-query -> defquery

  • Will conflict with Clara
  • Seems like a case where we actually want def since queries are usually functions and not Var assignments

def-tuple-session -> session

  • Will conflict with Clara
  • Should be OK if we are confident there aren't many cases where we'd want to define a Clara-syntax session alongside a tuple-syntax session

schema-insert -> ???

  • Hard to decide without first knowing whether we require a schema. If we do, this would seem to be the default insert, so we could go with insert and rename insert to insert-raw or something.

insert -> insert-raw???

  • See above

Note: Once we have the schema-insert equivalent of retract, suggest we follow whatever conventions we set up for insert.

defaction -> ???

  • This is poorly implemented but part of the API. Simply inserts and removes facts in between firings. Dropping def might be ok here but it's kind of nice with it.

Attribute-only in a vector in LHS makes rule never fire

Does not fire or throw any kind of error:

(def-tuple-rule add-item-cleanup
  {:group :cleanup}
  [?action <- [:add-todo-action]]
  =>
  (println "Action cleanup")
  (retract! ?action))

Works fine:

(def-tuple-rule add-item-cleanup
  {:group :cleanup}
  [?action <- :add-todo-action]
  =>
  (println "Action cleanup")
  (retract! ?action))

Explore creating facilities for working with sets of matched/accumulated facts within rules

From Mike:
the equivalent SQL is select * from TodoItem where id in (57,49,33,22,7)

(we do that kind of query all the time in ORMS -- have an ordered list, then pass it in as a "parameter" to another query that fetches just those items)

Drools has an "in" construct, do not know if that can bind to a collection, but think it does

CheeseCounter( cheese memberOf $matureCheeses )

ToDoList(?ids:=ids)
?items: ToDoItem(id memberOf $ids)

Hydrate one-to-many facts

Should be relatively straightforward to implement. We maintain a one-to-many fact type distinction throughout the code so probably all that is needed is to performantly identify one to many facts at hydration time (maybe in listener ns) and group them.

Define agenda groups by namespace

The hash-map we receive from activation-group-fn includes the following meta-information.

{:ns-name libx.perf-tuple 
 :lhs [{:type :add-todo-action :constraints [] :fact-binding :?action}]
 :rhs (do (println Action cleanup) (retract! ?action))
 :props {:group :cleanup :salience -100} 
 :name libx.perf-tuple/add-item-cleanup}

Seems like there's a lot we can do with this. It may be a place to add additional meta-information in the future as well.

Whatever we return from this function, the activation-group-sort-fn receives as input. Currently we're returning :group and :salience.

Part of the motivation for this issue is that rules read a little worse when defining this meta information as the first argument to a rule:

(def-tuple-rule acc-all-visible
  {:group :report :salience -100}
  [?count <- (acc/count) :from [:todo/title]]
  [:test (> ?count 0)]
  =>
  (println "Reporting count" ?count)
  (insert! [-1 :todo/count ?count]))

What we have works and is probably ok for now, but we should know that we can define groups by namespace, rule names, and other factors without having to add meta-information to them.

Drag and drop example?

Might be nice to have as a simple standalone example. Code we have for it uses re-frame so would just need to convert views to 0.1.0 API. We would need to add a few rules for subscriptions otherwise they would be the same.

Schema enforcement / garbage collection perf

Rules of the following form appear unviable for sessions with > 10,000 facts:

(cr/defrule remove-older-one-to-one-facts
  {:super true :salience 100}
  [?fact1 <- :one-to-one (= ?e1 (:e this)) (= ?a1 (:a this)) (= ?t1 (:t this))]
  [?fact2 <- :one-to-one (= ?e1 (:e this)) (= ?a1 (:a this)) (> ?t1 (:t this))]
  [:test (not= ?fact1 ?fact2)]

Handle actions with rules

From Mike:
observation: rules.cljs line 38 -- the remove-toggle-complete-when-all-todos-done would seem to be redundant if you are already inserting and retract action facts in the dispatcher. Are you? If removal of actions were done in rules, it should just be a low-salience rule that cleans it up absolutely, not one that tests if "the work is done"

clear-completed-action-is-done-when-no-done-todos is the same as remove-toggle-complete... The dispatch pipeline should remove after fire all events, or a low-salience rule should clear up any action once all other rules have fired

Need to resolve aliased and fully-qualified symbols in macros

Currently we assume users have referred symbols from the DSL namespace. When analyzing LHS syntax, we check for their unqualified existence and "resolve" them to precept.dsl by prepending "precept.dsl/" and call eval to get the expansion of the fully qualified symbol.

This will not work if a user aliases or fully qualifies any DSL macro.

For CLJ suggest using ns-resolve though I'm fuzzy on whether this works at compile time. For CLJS suggest looking at Planck's repl.cljs ns https://github.com/mfikes/planck/blob/master/planck-cljs/src/planck/repl.cljs#L354.

Activation groups should not be a flat list

Suggest the following input list for public API.

[:first [:some :group :names] :last]]

We should be able to create our own data structure based on this version that is more performant. e.g

{:first 0 :some 1 :group 1 :names 1 :last 2}

Identity and uniqueness

We are having to create excessive schema entries for attributes and marking them as :unique/identity in order to have them be one-to-one. Part of this stems from my misunderstanding of :unique/identity. :)

There's a larger issue, which is that sometimes we want attributes to serve as identifiers. For example, we will probably never care about making the mouse an entity. We end up using :mouse/x :mouse/y as the id and grabbing the values (never the eid). How many cases like these there will be, I'm not sure.

Here's my attempt to define things clearly:

  • if there can only be one occurrence of an attribute for an entity ("a todo has one title"), that is a one to one relationship.
  • if there can be multiple occurrences of the same attribute for an entity ("a todo list has many todos"), that is a one to many relationship
  • if comparing across entities there is an attribute for which a value uniquely identifies that entity and that excludes other entities from possessing that same attribute/value pairing (":username foobar"), that relationship is unique identity (because of its uniqueness, it can be used to identify an entity) This means that if todo/title is unique-identity and I want to find a todo/title with the value "Hey", I can safely expect to only receive one match.
  • if comparing across entities there is an attribute whose value may not be overwritten (upserted), that attribute is a unique value attribute. The only difference between unique value and unique identity attribute is the insertion behavior.

From Datomic's documentation:

:db.unique/value - only one entity can have a given value for this attribute. Attempts to assert a duplicate value for the same attribute for a different entity id will fail. More documentation on unique values is available here.
:db.unique/identity - only one entity can have a given value for this attribute and "upsert" is enabled; attempts to insert a duplicate value for a temporary entity id will cause all attributes associated with that temporary id to be merged with the entity already in the database. More documentation on unique identities is available here.

What we appear to want and lack is a designation of a unique attribute -- an attribute that always resolves to a single entity. I don't think using Datomic's schema is the right way to go about it. Instead, I would propose we define a function that allows us to specify a vector of attributes that should be maintained according to "unique attribute". A function could take this vector and generate the needed rules to find facts two facts with that attribute and remove the older one. Performance concerns abound with this so let me know if you think it's worth trying.

Maintain and generate lists at API level

We should be able to have an api for the following:

(def-tuple-rule create-list-of-visible-todos
  {:group :report}
  [?eids <- (by-fact-id :e) :from [:todo/visible]]
  [:test (seq ?eids)]
  =>
  (trace "List!" ?eids)
  (insert! [(guid) :todos/by-last-modified*order ?eids])
  (doseq [x ?eids]
    (insert! [(guid) :todos/by-last-modified*eid x])))

(def-tuple-rule update-list-of-visible-todos
  {:group :report}
  [[_ :todos/by-last-modified*eid ?e]]
  [?entity <- (acc/all) :from [?e :all]] ;; TODO. Can substitute entity
  =>
  (trace "Entity list!" ?entity)
  (insert! [(guid) :todos/by-last-modified*item ?entity]))

(defsub :task-list
  [[_ :todos/by-last-modified*order ?eids]]
  [?items <- (acc/all :v) :from [:todos/by-last-modified*item]]
  [[_ :active-count ?active-count]]
  [:test (seq ?eids)]
  =>
  (let [items (group-by :e (flatten ?items))
        ordered (vals (select-keys items (into [] ?eids)))
        entities (util/entity-Tuples->entity-maps ordered)]
    (trace "Entities" entities)
    {:visible-todos entities
     :all-complete? (= 0 ?active-count)}))

The end result is a list of entities in a specified order. We could have rules that do this under the covers and surface the resulting list as a fact.

Cannot unmark todo "done"

To reproduce:
Add todo
Mark done
Click mark done again

Expected:
Todo should be active

Actual:
Todo is still done
"Done count" decreases with each click (even into negative numbers"

Dispatch sync needed on start

Appears there is a race condition between defining the session and mounting the components. Suggest start fn takes a callback as last argument and add dispatch-sync.

Edit: actually might just be trying to insert facts before the session has been swapped into the state atom.

Add defsub macro

Currently working on this in branch defsub. Will need to copy code out in order to merge because branch also contains work on nested macros #40 that is not essential to its implementation.

I've written the macro but it isn't working for some (probably small) reason. The following is the current subscription syntax written with defrule and the new proposed defsub macro that will expand to it:

(def-tuple-rule subs-footer-controls
  {:group :report}
  [:exists [?e ::sub/request :footer]]
  [[_ :done-count ?done-count]]
  [[_ :active-count ?active-count]]
  [[_ :ui/visibility-filter ?visibility-filter]]
  =>
  (insert!
    [?e ::sub/response
        {:active-count ?active-count
         :done-count ?done-count
         :visibility-filter ?visibility-filter}]))
(defsub :footer
   [[_ :done-count ?done-count]]
   [[_ :active-count ?active-count]]
   [[_ :ui/visibility-filter ?visibility-filter]]
   =>
   {:active-count ?active-count
    :done-count ?done-count
    :visibility-filter ?visibility-filter})

Note this should also remove the need to require the spec.sub namespace in rules (or anywhere else within an application).

Calling macroexpand on the macro I've written for this appears to yield identical output to the def-tuple-rule version. For some reason however the new macro version does not fire.

Remove dead code pertaining to action pattern

  • util.cljc - Remove all code pertaining to :action type facts
  • tuplerules/macros - Remove store-action
  • impl.rules - Remove action-cleanup rule

Any pertinent tests should also be removed.

Add fact id at insertion time instead of through rule

Instead of issuing an id to every fact that is inserted in the same batch, we are opting to identify each fact in the batch with a unique incrementing id number that can be referenced from rules. This appears much more useful to us the our fourth slot in our fact Tuple than a transaction id at the moment.

Should also change tx-id and :t shorthand in e a v t params of Tuple to reflect this is not a transaction but a fact count. Makes shorthand a little bit more challenging but we'll get there. Shorthand is obfuscated from the user but we will probably want to have an equivalent of tx-id or t standing for transaction-id for fact-id.

Dev tools

Think this would be a good investment and there's a lot of exciting things we could do in this space.

Perhaps the most obvious tool though perhaps not the most useful for rules would be a redux-devtools clone that shows the changes to our reagent state atom. Because we are treating actions as special and only inserting facts into the session via actions, we can easily and quickly mimic the way redux-devtools models actions, though it may not be the best implementation long term.

Rule registry may not be getting written to in CLJS

Have seen the rules atom evaluate to [] in CLJS. Was on a branch with a lot of changes though. Need to verify this is the case on master and see if the same occurs in CLJ.

Our only current use for the registry is internal and inessential to our implementation (evidently). We would like to have it available for future use, particularly to ns-unmap all rules while developing to ensure the only rules loaded in the REPL are the ones currently defined.

Problem may be that we are registering in CLJ and trying to deref the atom in CLJS. Clara uses something nearly identical to this, but I don't know whether the value of their registry is accessible from CLJS. This isn't a problem for them but if we want tooling that uses this information in CLJS I think we'd need to be able to have the values accessible from CLJS somehow.

Schema-based insert test

Something of a start on this in schema-test.cljc. Once we have set up might aid development of additional schema support, in particular one-to-many vs one to one.

Can't express :type and a binding on attribute simultaneously

(cr/defrule remove-older-unique-identity-facts
  {:super true :salience 100}
  [:unique-identity (= ?a1 (:a this)) (= ?t1 (:t this))]
  [?fact2 <- :unique-identity (= ?a1 (:a this)) (= ?t2 (:t this))]
  [:test (> ?t1 ?t2)]
  =>
  (trace (str "SCHEMA MAINT - :unique-identity" ?t1 " is greater than " ?t2))
  (retract! ?fact2))

Currently no way to do this using our positional syntax. Not sure what a good syntax would be for this.

Expression reads in English as something like "when there's a unique-identity type of fact, bind the value of its attribute to ?a1".

Possibilities for new syntax:

[[_ (and :unique-identity ?a1) _ ?fact-id]]
[[_ [:unique-identity :as ?a1] _ ?fact-id]]

We can do this with current syntax. Not sure if performance is worse.

[[?e1 :unique-identity _ ?fact-id]]
[[?e1 ?a1 _ ?fact-id]]

Full stack example

We have a start on this using sente for client-server communication and Datomic for db.

Not sure what the example app should be. Full stack todo app? ๐Ÿ˜…

A simple example could simply show persistence with Datomic. Fancier could add rules sessions on the client and the server.

Temp-ids

Forget why we don't have this already. Could implement the same way Datomic does, i.e. automatically assign (util/guid) to any fact inserted with a negative int and allow references with the same negative int to resolve to the same guid.

Example:

(insert! [[-1 :todo/title "foo"]
              [-1 :todo/done true]
              [-2 :todo/title "bar"]
              [-2 :todo/done true]])

Clara and Apache 2.0 license

Clara uses the Apache 2.0 license.

I opened a discussion about changing it to MIT or EPL here. We'll see what they say.

As I stated in the issue I regard the problem to be public perception. If this information is reliable, it seems the only difference between Apache 2.0 and MIT is that you can't have a multicolored feather logo or the Apache name in your product.

Match on tuple with keyword in first position does not expand correctly

(macroexpand
'(def-tuple-rule action-cleanup-2 {:group :cleanup}
  [?fact <- [:this-tick :all ?v]]
  =>
  (trace "CLEANING this-tick fact because transient" ?fact)
  (cr/retract! ?fact)))
=>
(def
 action-cleanup-2
 (clojure.core/cond->
  {:ns-name (quote libx.tuplerules),
   :lhs (quote [{:type :all, :constraints [(= ?v (:v this))], :fact-binding :?fact}]),
   :rhs (quote (do (trace "CLEANING this-tick fact because transient" ?fact) (cr/retract! ?fact))),
   :props {:group :cleanup}}
  action-cleanup-2
  (clojure.core/assoc :name "libx.tuplerules/action-cleanup-2")
  nil
  (clojure.core/assoc :doc nil)))

Constraints should include (= :this-tick (:e this)).

Clara equivalent:

(macroexpand
  '(cr/defrule action-cleanup-2
     {:group :cleanup}
     [:all (= :this-tick (:e this) (= ?v (:v this)))]
     =>
    (trace "CLEANING this-tick fact because transient" ?fact)
    (cr/retract! ?fact)))
=>
(def
 action-cleanup-2
 (clojure.core/cond->
  {:ns-name (quote libx.tuplerules),
   :lhs (quote [{:type :all, :constraints [(= :this-tick (:e this) (= ?v (:v this)))]}]),
   :rhs (quote (do (trace "CLEANING this-tick fact because transient" ?fact) (cr/retract! ?fact))),
   :props {:group :cleanup}}
  action-cleanup-2
  (clojure.core/assoc :name "libx.tuplerules/action-cleanup-2")
  nil
  (clojure.core/assoc :doc nil)))

Determine support for Datomic-style map insertions

With Datomic, users typically create transaction data using a map syntax instead of a tuple syntax. Here's an example that uses this and makes defining entities easier with a helper function

(defn todo [title]
 {:db/id (guid) :todo/title title :todo/time-created (.now js/Date) :todo/done false})

Maps also allow namespace-map syntax that will be released in Clojure 1.9 and already appears in CLJS 1.9.

(ns facts
  (:require [libx.spec.todo :as todo])

(defn todo [title]
  (assoc
    #::todo{:title title :time-created (.now js/Date) :done false}
    :db/id (guid))

I think we should try support this even though it may slow down our inserts/retracts. We are already trying to parse insert arguments as vectors, vectors of vectors, tuple records, vectors of tuple records, vectors with tuple records in 3rd position and vectors of vectors with tuple records in 3rd position.

Determine valid inputs for `insert`, `insert!`

Since we have moved to underlying facts being Tuple record instances, our insert/retract API must convert any input it receives into records. We have this working in the simplest for and for cases in which the user wants to nest a fact inside the :v slot of a Tuple.

The issues here seem to be deciding what we accept as input. Some examples that seem necessary to support:

;; Where ?a-visible-todo will be Tuple record
(insert! [?e :todo/visible ?a-visible-todo]) 

;; Where we return maps that get written to the store atom
(insert-unconditional! [[?e ::sub/footer {:active-count ?active-count                                                           
                                                                  :done-count ?done-count}
;; Where we define entities in terms of maps (usually via functions)
(insert [{:db/id (guid) :todo/title "Hey" :todo/visible :tag :todo/done :tag}])
             {:db/id (guid) :todo/title "Yo" :todo/visible :tag :todo/done :tag}

Support/add example of ordered list of facts

From Mike:
:todo/listorder [57, 49, 33, 22, 7]

it's going to be common and one of the places where we could "blow up" because of the unordered nature of relational tuples. Datomic should have the exact same problem with intrinsically unordered list items and people must solve it the same way

[?e :todo/listorder ?ids]
?list <- ...normal accumulator stuff you are doing to get unordered list
-> walk through the list order and pull in items by id from list

S-exprs in rules don't expand to proper Clara syntax

(def-tuple-rule handle-start-todo-edit
  {:group :action}
  [[_ :todo/start-edit-action ?action]]
  [[(:id ?action) :todo/title ?v]]
  =>
  (trace "Responding to edit request" (:id ?action) ?v)
  (insert-unconditional! [(:id ?action) :todo/edit ?v]))

The second condition should expand to [:todo/title (= (:id ?action) (:e this)) ...]. Currently it appears to pass directly through as an s-expr and gets ignored by clara's parser.

Marking single todo "done" removes it

Appears to happen only after using "clear completed".

To reproduce:
Add todos
Mark one "done"
Use clear completed button
Try to mark a todo done

Expected: Should mark done

Actual: Deletes it

Activation groups may activate out of order

We currently use the following activation groups:

[:action :calc :report :cleanup]

We've observed that inserting a fact in :calc that a rule in :action matches will cause the :action rule to fire. This is not our understanding of how activation groups should work in Clara.

Clara PR #288 confirms CLJS support for activation-group-fn, activation-group-sort-fn and salience. Given this I propose we investigate the following:

  1. Test with Clara only to see whether the bug is a result of our implementation
  2. Test in CLJS to see if it is CLJS specific
  3. File an issue with Clara if necessary

If it turns out our understanding of activation groups is faulty, Clara has an open issue that proposes adding agenda groups that behave similar or identical to Drools/Jess, using "focus" here: cerner/clara-rules#107. This may be more in line with the functionality we expect to have currently.

Rename transaction id to fact id

Currently we are not assigning ids to facts based upon the group that they are inserted into the session with. In other words, if we insert 100 facts into a session, each fact will have a separate id. Because this is more useful to us than a transaction id for the foreseeable future, we should rename all instances of t tx-id etc. to fact-id and reintroduce the concept of assigning ids to transactions when the need arises.

Can't update a fact without causing infinite loop in action handler

We have this in the todomvc example:

:on-change #(then :todo/toggle-done-action {:id id})}]
(def-tuple-rule handle-toggle-done-action
  {:group :action :salience -100}
  [:exists [?e :todo/toggle-done-action ?v ?action-tx]]
  [:exists [(:id ?v) :todo/done ?bool]]
  =>
  (trace "Responding to toggle done action " [(:id ?v) :todo/done (not ?bool)])
  (insert-unconditional! [(:id ?v) :todo/done (not ?bool)]))

Loop is caused by second condition in the above rule, which we need in this case to find the fact to update. We continue to match on the :todo/done that we insert, so it is seems like the :exists operator isn't counting the second condition as "already matched" for some reason. More on clara and :exists here cerner/clara-rules#130 and https://groups.google.com/forum/?hl=en#!topic/clara-rules/PXY2nU3ZyYc.

We are inserting unconditionally because any facts containing -action in their attribute name are removed at the end of every firing. There is a high-salience, agenda-less rule that removes the old :done fact for us because it's defined as :unique-value.

@mikegai How can we solve this without receiving more information from the action coming in from the view?

Support error messages via rules for unique/value attributes

This came up specifically in relation to uniqueness and how we handle it. It seems we do want to support the equivalent of db.unique/value, where the behavior is not upsert but to throw an error.

This brings us to a very important part of the API that we need to beef up, which is errors in general. Edit: Not actually errors but...you know what I mean

For this, we'd like to surface error messages via rules, allowing users to handle them via rules. The range of possibilities here is huge and very exciting.

For now the goal is to implement this when a :unique/value attribute is about to be overwritten. Should be relatively straightforward as we detect this inside of insert, so we can easily add a new "error" fact. We should consider a precept.spec.error ns if we need it for well-defined "error facts".

Edit todo should write to working memory, not local state atom

From Mike:
questions: line 27 of views.cljs - you are not subscribing. That is just the equivalent of internal state for the component? Shouldn't all of that be in the working memory? I imagine that is left-over from the original of course, just trying to find intent.

Subscription results in view model should respect schema

I passed over this when refactoring schema support because I was anticipating changes to subscriptions that we decided not to implement:

(defn apply-additions-to-view-model! [tuples]
  (doseq [[e a v] tuples]
    (let [ancestry (@s/ancestors-fn a)]
      (cond
        (ancestry :one-to-one) (swap! s/store assoc-in [e a] v)
        (ancestry :one-to-many) (swap! s/store update-in [e a] conj v)))))

(defn apply-removals-to-view-model! [tuples]
  (doseq [[e a v] tuples]
    (let [ancestry (@s/ancestors-fn a)]
      (cond
        (ancestry :one-to-one) (swap! s/store util/dissoc-in [e a])
        (ancestry :one-to-many) (swap! s/store update-in [e a] (fn [xs] (remove #(= v %) xs)))))))

We can identify subscriptions by looking up their eid in the registry or checking attribute namespace for precept.spec.sub.

Ideally we could issue a recursive call to these functions if the value of a subscription response has the shape:

{:some-key Tuple or [Tuple]}

The result needs to look like:

{eid {::sub/response {:some-key [{:db/id 123 :attr [1 2 3]}]
                                    :other-key 42}}}

We can avoid new conditions by converting the Tuples up front. We must ensure however that we're calling a function that does not collide keys for :one-to-many attributes.

We can't call swap! with a function that updates the path [e a]. Whether a recursive or nonrecursive call we'd need to target [e a sub-response-kw].

Suggest rewriting Tuples->maps or equivalent to use @state/ancestors fn and return the objectified form. Should then be able to use the same swap strategies we already have on the subscription path directly.

We may avoid an antipattern by adding the subscription type to the ancestry. We could also factor out the cond statement into a function that takes a path argument.

Metarules

While brainstorming solutions for #45 I thought about using rules to program rules.

Suppose we had the following metarule that operates over something like our DSL's AST:

(meta-rule 
  [[?rule ::special-form/entity ?ref]]
  [[?ref :arg-1 ?arg-1]]
  [[?rule ::rhs/inserted-bound-variable ?e]]
  [[?result <- (acc/all :e) :from [?arg-1 :all]]]
  =>
  (insert [(guid) :your-entity-result ?result])

Here, as in other approaches to solving #45, there's an issue with "handing back" any result we would generate at the framework level to the user. Part of this seems related to the inability to distinguish a fact's identity given syntax alone. For #45 we may resolve this by requiring users to supply an attribute that acts as a kind of callback. Seems we need to know what attribute to assign the list value to, and the user needs to know that as well so they can grab it.

Anyway. These are merely thoughts for now, but exciting ones given that the resolution for #45 may entail dynamic rule generation. Combined with the rule-registry we already have, metaprogramming with rules seems not so far out of reach.

Nested macros in CLJS

Work on this exists in branch defsub. It's not related to defsub at all though (oops).

I've found several solutions that work in CLJ and use eval. I haven't been able to get any of them to work in CLJS. For now I am focusing efforts elsewhere and awaiting responses to the SO question I've posted on the topic here:
http://stackoverflow.com/questions/43985923/how-can-i-force-evaluation-of-nested-macros-in-clojurescript

Some links to similar questions that seem worth investigating:
http://stackoverflow.com/questions/29186212/proper-way-of-evaluating-symbols-in-clojure-macros?noredirect=1&lq=1

http://stackoverflow.com/questions/7684656/clojure-eval-code-in-different-namespace

I've also read relevant posts here and attempted several implementations of the "macro tower" strategy:
http://blog.fikesfarm.com/posts/2015-12-18-clojurescript-macro-tower-and-loop.html
http://blog.fikesfarm.com/posts/2016-03-04-collapsing-macro-tower.html
https://github.com/clojure/clojurescript/wiki/Differences-from-Clojure#macros

Somehow cljs.test appears to have solved this. From http://blog.fikesfarm.com/posts/2016-02-27-testing-with-planck.html:

A lot of cljs.test is macros, like deftest and is, and one key aspect is that these macros call other macros in the cljs.test namespace. While this is cool in Clojure, it is verboten in ClojureScript. This alone means one does not simply (require 'cljs.test) in a bootstrap REPL. This can be solved, though, by constructing a macro tower.

Sounds like the verboten bit and the need to construct a macro tower applies only to self-hosted/bootstrapped CLJS, though it's not entirely clear to me.

In our case we are already macro towering via tuplerules.cljc and macros.clj. Speculatively both the problem and the solution lie therein. In the macros test example, I have tried both making the main macro file a cljc file and and clj file, and the results are different. The results are different also when including clara.rules.compiler to call functions that interrogate the status of CLJS compilation. Note that file must be called from a CLJ file, because compiler itself is a CLJ file.

What macros/fns belong in what namespaces

Have been deferring this while developing. This all depends on the names we settle on. Using the current names and suggesting the following.

tuplerules

  • Rename to rules
  • def-tuple-rule
  • def-tuple-query
  • def-tuple-session

core

  • schema-insert
  • insert
  • retract
  • start!
  • then
  • subscribe
  • entity
  • facts-where
  • entities-where

query

  • Doesn't currently exist
  • All query helpers currently in util that are mainly for internal use and injected intodef-tuple-session (qa-,qav- etc.). Would require changing def-tuple-session to require libx.query instead of libx.util

Suggest removing:

  • insert-fire, retract-fire - As of 0.1.0 there's not much to justify their existence

Suggest adding:

  • fire-rules - Lame but suggest referencing Clara here. It's one of two macros/functions that we would probably want when developing an app.
  • query - Again we'd need to reference Clara. Suggest putting in core as q. Would conform to Datomic and datascript API:
    (libx/q my-session some-query-defined-with-defquery)

Suggest making private:

  • This is hard for me because I don't like when libraries do this but with everything public as it is now, it's not clear what our API actually is. core seems the main ns where we should consider making functions private. If we really want something there to remain public suggest moving to util.

Create README.md

Keep in mind:

  • We come from a rules programming background (Clara, Mike's Dreamer framework in Scala, Drools). It's easy forget most of our audience has yet to encounter pattern matching, unification, LHS, RHS, working memory, and so on. We shouldn't talk about any of these things as if the reader has encountered these concepts before. More than likely they haven't had the chance.

Considering adding sections:

  • Why Clojure? - Clara has a section on this here that highlights the features Clojure has that other languages don't that led them to write Clara in Clojure. They also suggest reasons why Clojure is a good language to work in generally, independent of their decision to write Clara in it. Suggest we take a similar approach as our audience might not be familiar with Clojure and what it offers when it comes to general purpose programming.
  • Pattern matching - Many programmers may not be familiar with this. We should not expect to be able to put our syntax in front of people without any explanation and have it make sense. There is some overlap with documentation for the DSL (see below). We don't want to go that far in depth, but any example code in the readme should at least be annotated in such a way that it can be understood. We can link to the DSL wiki page so people who are interested in knowing more about it can jump to that information immediately. E.g. annotated example, link "Learn more about Precept's DSL/writing rules" (DSL may be a foreign concept).

We will also need to create documentation for the DSL. I'm thinking a separate markdown document in the Wiki that augments what we have in the docs, goes into more detail, provides examples, English translations of LHS of rules and the like.

Define API/namespace for accumulators

There are two related issues here that I will combine into one for the time being.

We still require a dependency on Clara in part because we do not wrap its accumulator library. If we exposed their accumulator namespace API through one of our own, fire-rules would be the only part of the API that would mandate a Clara dependency.

We currently have one custom accumulator defined inline in the todomvc rules file that accumulates facts by their fact ids in ascending order. It either returns the whole fact or the value of the key passed in (:e, :a, or :v).

If we are to have a more robust list API (#45) we should consider making a namespace for accumulators such as these. Note that until #40 is fixed there is not much we can do that is syntactically different from Clara's accumulators. Once it is resolved we might want to place the entity macro that accumulates all attributes associated with an eid in that namespace.

Schema maintenance at entity level

In todomvc, we enforce unique titles. However, because a todo is created with other attributes (:done, :visible), those facts end up being inserted, so we can end up with the following:

"Entities" [{:db/id #uuid "4ac207db-9175-4334-818b-a42ea9967ad5", :todo/done false, :todo/visible :tag} {:db/id #uuid "458162a9-ba9c-4778-a8a8-e6f73087c82a", :todo/title "abcd", :todo/done false, :todo/visible :tag}]
image

The blank todo shows in this case because an additional todo with a title of "abcd" had been added, then removed because it is a unique attribute, leaving behind :done and :visible. :visible is used to detect what todos should be shown.

Logging the issue as a bug for now since it could be worked around at the application level. It's definitely something we want to help users deal with at the API level. Will brainstorm options later. One that comes to mind is a "component" designation from Datomic though I don't think it's an exact fit for this case.

Client-side schema

Starting assumptions:

  1. We currently derive the cardinality and uniqueness of attributes from a Datomic schema that only covers facts backed by persistent storage
  2. For data that is not persisted, there is a need to insert facts for an entity where more than one value is associated with an attribute (e.g [1 :some-list "a"] [1 :some-list "b"]).
  3. All facts are enforced as one-to-one by default
  4. Maintaining separation between client-side data and persistent data is beneficial because it allows us to write facts to a database
  5. It is not necessary to enumerate every client-side fact in order to separate them from persistent facts so long as we know the persistent attributes

Assuming the above, proposing def-tuple-session receives a list of one-to-many attributes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.