A declarative programming framework
Docs | Example | Issues | Roadmap | Contributing
"You need only specify what you require, not how it must be achieved." - Out of the Tar Pit
"If you want everything to be familiar, you will never learn anything new, because it can't be significantly different from what you already know." - Rich Hickey, Simple Made Easy
(rule inc-count-when-tick
[[_ :tick]]
[[?e :count ?v]]
=>
(insert! [?e :count (inc ?v)]))
(defsub :count)
[[_ :count ?v]]
=>
{:count ?v}
(session my-session 'counter-ns)
(defn counter []
(let [count @(subscribe [:count])]
[:div count]))
(.setTimeout js/window #(then [:transient :tick true]) 1000)
(reagent/render [counter] (.-body js/document))
(start! {:session my-session :facts [[:global :count 0]]})
“In the ideal world, we are not concerned with performance, and our language and infrastructure provide all the general support we desire.” - Out of the Tar Pit, emphasis added
Precept wraps Clara, a ground-up implementation of the Rete algorithm in Clojure and Clojurescript.
We believe Clara is the first to have implemented a rules engine that runs in the browser. Had this happened earlier, a truly declarative approach to front-end web development might already have become popular.
State in Precept is more or less a "bag of facts". There is no tree structure to reason over or organize. The session just contains a bunch of tuples that can be grabbed at will. The fact of a key-code is on the same level as a username.
All facts from the rules session are synced to a reactive view model. Components are rerendered only when the data they subscribe to changes.
Subscription results are returned as maps. Tuples are great for pattern matching, but maps are a better fit for components to generate markup from and for programming languages to iterate over. As a result, working with data in the view layer is the same as it is in most any frontend library or framework.
Precept enforces cardinality and uniqueness according to user-supplied, Datomic-format schemas. This allows the modeling of one-to-many relationships with eav tuples:
;; schema.cljs
(attribute :list/item
:db/cardinality :db.cardinality/one-to-many)
;; rules.cljs
(insert! [[123 :list/item "foo"]
[123 :list/item "bar"]
[123 :list/item "baz"])
Components receive :list/item
as a list:
{:db/id 123 :list/item ["foo" "bar" "baz"]}
Unique attributes are handled using the same semantics as Datomic for :db.unique/identity and :db.unique/value. When there is a conflict, instead of throwing an error, Precept inserts facts about the error into the session so they it may be handled or resolved through rules.
Precept supports both a :db-schema
and a :client-schema
to allow users to distinguish persistent facts from non-persistent facts. They're both enforced the same way.
Precept can hand you the facts you want to persist, while still allowing you to define schema attributes your client-side only data.
The project board contains the most up-to-date information about what features are being discussed, prioritized, and worked on. Here's a few we're excited about.
Because we use a rules engine, we know what changes from one state to the next. This means we don't need React and its diff algorithm to figure out "What changed?" for us. If we declare a view in the right hand side of a rule, we can render it when the facts it cares about change. We don't even need the concept of subscriptions.
Rules are pluggable. We should be able to use general purpose rulesets the same way we use libraries. E.g. drag and drop, typeahead, etc. Rulesets can be authored by the community. Precept can add have its own rulesets that can be turned on or off when defining a session.
Because Clara's sessions are immutable, we can store each one and cycle through them. Clara provides tools for inspecting sessions that show what rules fired, why they fired, what facts were inserted, which were retracted, which rule inserted what, which rule retracted what, and so on.
In addition, changes to Precept's view model can visualized and tracked just like Redux DevTools.
Precept aims to enable teams to build increasingly game-like UIs. This sometimes requires algorithms for path-finding, tweening, collision detection, and distance calculation. We want to write applications where talking about these things is trivial. That means never having to fall back to imperative programming, while at the same time having the performance it provides. We're working to support declarative statements like (<- ?my-trip (distance ?paris ?london))
that allow us to focus on what, not how, by calling performant, general-purpose algorithms under the covers.
-
Dmitri Sotnikov and the Luminus framework
-
Bruce Hauman and Figwheel