conarrative / precept Goto Github PK
View Code? Open in Web Editor NEWA declarative programming framework
License: MIT License
A declarative programming framework
License: MIT License
Should be straightforward to write as we are already testing for additions as of #52.
Suggest the following:
then
-> โ
then
/consequence
/rhs
.subscribe
-> โ
deflogical
-> define
def
. Problematically logical
doesn't seem to make a whole lot of sense without def
in front.def-tuple-rule
-> rule
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
def
since queries are usually functions and not Var assignmentsdef-tuple-session
-> session
schema-insert
-> ???
insert
and rename insert
to insert-raw
or something.insert
-> insert-raw
???
Note: Once we have the schema-insert
equivalent of retract, suggest we follow whatever conventions we set up for insert.
defaction
-> ???
def
might be ok here but it's kind of nice with it.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))
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)
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.
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.
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.
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)]
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
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.
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}
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:
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.
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.
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"
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.
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.
:action
type factsstore-action
action-cleanup
ruleAny pertinent tests should also be removed.
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
.
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.
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.
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.
See https://github.com/viebel/klipse
Guessing that we wouldn't be able to edit rules and see updates. Supports reagent and rendering DOM though.
(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]]
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.
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 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.
(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)))
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.
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}
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
(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.
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
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:
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.
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.
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?
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".
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.
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.
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.
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.
Currently just removing the attribute
May significantly speed up macro writing/debugging
http://stackoverflow.com/questions/13555980/how-do-i-compile-clojurescript-forms-from-within-clojure?rq=1
We need this badly. Should probably be in CLJS.
Have been deferring this while developing. This all depends on the names we settle on. Using the current names and suggesting the following.
tuplerules
rules
def-tuple-rule
def-tuple-query
def-tuple-session
core
schema-insert
insert
retract
start!
then
subscribe
entity
facts-where
entities-where
query
def-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 existenceSuggest 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:
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
.should probably rename :footer to :footer-controls to show more one-to-one between control name and "sub"/viewmodel/whatever name
[2:50]
(or vice versa, rename component to footer
)
Keep in mind:
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:
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.
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.
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}]
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.
Starting assumptions:
[1 :some-list "a"] [1 :some-list "b"]
).Assuming the above, proposing def-tuple-session receives a list of one-to-many attributes.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.