To keep thing simple and see where it leads us we postulate here that a rule or a rule set is just a function of facts producing new facts. No more, no less.
Another "weaker" constraint of the design was to use
Datalog/Datascript Query to produce a "Result Set" from the "Input
Facts" and to generate the "Output Facts" from the "Result Set" in a
sufficiently flexible way. The mapcat
- aka flatMap
-style
concatenation of the "Output Facts" independently generated from each
row of the "Result Set" appeared then to be a natural choice.
Note that it does not always make sense to mix/conflate the input facts and the output facts, here illustrated by use of two different human languages:
(let [facts [[1 :is "odd"]
[2 :is "even"]]
rules (defrules
{:find [?number]
;; But only even numbers from the set of English
;; facts by Datalog query:
:when [[?number :is "even"]]
;; For each number produce this set of facts in
;; German:
:then [[?number :ist "gerade"]
[(inc ?number) :ist "ungerade"]
[(dec ?number) :ist "ungerade"]]})]
(rules facts))
;; =>
#{[1 :ist "ungerade"]
[2 :ist "gerade"]
[3 :ist "ungerade"]})))
Still many rule engines insist on "inserting" deduced facts into the very database that was "queried".
Would such a calculus be useful or is it just mind gymnastics just to
reinvent
the
wheel? Here is an
example of Binary Rule that compares two datasets, en
and de
, to
deduce a translation between English and German:
(let [en [[1 :is "odd"]
[2 :is "even"]]
de [[1 :ist "ungerade"]
[2 :ist "gerade"]]
tr (defrules
{:from [$en $de]
:find [?word ?wort]
:when [[$en ?n :is ?word]
[$de ?n :ist ?wort]]
:then [[?word :eqv ?wort]
[?wort :eqv ?word]]})]
(tr en de))
;; =>
#{["odd" :eqv "ungerade"]
["gerade" :eqv "even"]
["ungerade" :eqv "odd"]
["even" :eqv "gerade"]}
Think of composable programs with "Rules" operating on "Sets of Facts". Think of N-ary Rules taking zero, one or more "Sets of Facts" as arguments and producing even more of that as a result:
Rules:: Facts1 -> Facts2 -> ... -> FactsN
Think of select ... from facts1 join facts2 ...
. Or is a join
rather bad analogy because it is too "cartesian"? What kind of
expressions besides function application should be allowed? In other
words, what are the built-ins? Some set operations like union and
diff? Joins? A plain irreversible union that trows facts of
different domains in a single pot/set is probably not a good idea
type-wise.
First you may consider bumping the version in
project.clj
. Remember that you cannot overwrite non-SNAPSHOT
versions and what gets published, stays public. You habe been warned!
With that said, here is the memo:
$ lein clean
$ lein jar
$ lein test
$ lein deploy clojars
The artifact goes to
f0bec0d/regelwerk repository
so the user name will be f0bec0d
. The passwort is one of the
Deploy Tokens generated at Clojars, likely stored in your KeePass.
- Maybe one shlould read PAIP before even attempting ...
- The whole RDF Subject and inference is huge to swallow.
- See Datascript dialect of Datalog.
- Minikusari in less two dosen lines of code generates transaction datoms (facts) from the result set of a Datascript query.
- naga seems to be generating facts from rules.
- Souffle Lang uses plain *.facts for input- and output relations.
- Formulog delivers an executable JAR that transforms program "text" to fact database "text" bei executing Rules
- Crepe DSL in Rust that Acknowledges Souffle & Datalog for inspiration.
- Functional Production Rules
- Another unifier-based query engine
- Meander
- Knowledge Machine from around 1997 or before.
Maybe an interesting read:
- Better Together: Unifying Datalog and Equality Saturation PDF
- Is Datalog a good language for authorization? and the HN discussion
- Fixpoints for the Masses: Programming with First-Class Datalog Constraints proposes composable Datalog programs as Data.
Version 0.0.3-SNAPSHOT:
- Rules involving more than one dataset:
{:from [$a $b], :when [[$a ...], [$b ...]], :then [...]}
. - BREAKING: no more list-syntax for rules. Convert the lists to maps
with
:find
,:then
, and:when
keys in that sequence. - BREAKING: Singular form
(defrule vars expr where)
is no more supported. Use maps instead. - BREAKING:
(comment ...)
forms in Rules are no more handled specially. Use Reader Comment#_
instead. - DEPRECATED: A rule that returns any facts derived from an empty
dataset likely violates "empty graph lemma". Therefore the special
case of
{:then [...]}
akin to(constantly [...])
that returns facts "no matter what" must go. In the unlikely case that this edge case affects you, consider some seed- or ground facts in your dataset like["version" :ge "0.0.3"]
or["water" :is "wet"]
...
Copyright © 2022 Alexei Matveev alexei.matveev@gmail.com
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.