{"id":27389336,"url":"https://github.com/vvvvalvalval/datalog-rules","last_synced_at":"2025-04-13T19:14:18.161Z","repository":{"id":62437290,"uuid":"70498359","full_name":"vvvvalvalval/datalog-rules","owner":"vvvvalvalval","description":"Utilities for managing Datalog rulesets from Clojure","archived":false,"fork":false,"pushed_at":"2016-12-03T17:09:52.000Z","size":19,"stargazers_count":46,"open_issues_count":2,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-17T05:29:19.341Z","etag":null,"topics":["clojure","datalog","datascript","datomic"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vvvvalvalval.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-10T14:52:39.000Z","updated_at":"2024-02-10T02:52:03.000Z","dependencies_parsed_at":"2022-11-01T21:34:17.452Z","dependency_job_id":null,"html_url":"https://github.com/vvvvalvalval/datalog-rules","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvvvalvalval%2Fdatalog-rules","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvvvalvalval%2Fdatalog-rules/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvvvalvalval%2Fdatalog-rules/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvvvalvalval%2Fdatalog-rules/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vvvvalvalval","download_url":"https://codeload.github.com/vvvvalvalval/datalog-rules/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766751,"owners_count":21158301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","datalog","datascript","datomic"],"created_at":"2025-04-13T19:14:17.571Z","updated_at":"2025-04-13T19:14:18.155Z","avatar_url":"https://github.com/vvvvalvalval.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# datalog-rules\n\n\u003e Yeah it does!\n\u003e -- Anonymous Datomic user\n\nUtilities for managing [Datalog](http://docs.datomic.com/query.html) rulesets from Clojure.\n\n[![Clojars Project](https://img.shields.io/clojars/v/vvvvalvalval/datalog-rules.svg)](https://clojars.org/vvvvalvalval/datalog-rules)\n\nBy Datalog, we mean the rules language exposed by Datomic and Datascript \n (because of the data-orientation of Datalog, this library doesn't have any code dependency to either of those).\n\nProject status: alpha, subject to breaking changes.\n\n## Rationale\n\nRules are a powerful abstraction mechanism in the logic language that is Datalog,\n just like functions are a powerful abstraction mechanism in the procedural language that is Clojure.\n\nHowever, Datalog rules come with limited infrastructure, which leaves much to be desired in a Clojure environment:\n\n* As of Datomic 0.9.5530, Datalog queries accept only one ruleset input,\nwhich pressures you towards centralizing all your rules in one place. This can be bad for code cohesion.\n* For 'polymorphic' rules, this is even worse, as there is no way to separate interface declaration from implementation.\n* No query-able documentation.\n* In order to achieve performance, the same rule sometimes needs to be declared in 2 different orders, which leads to code duplication.\n\nThis library provides utilities to address these issues.\nIt does so by providing global rules registries, called *rulesets*, upon which you can register rules from various places\nin your code, in a way that is friendly to interactive development.\n\n## Usage\n\n### TL;DR\n\nHere's a complete usage example; see below for a detailed walkthrough.\n\n```clojure\n(require '[datalog-rules.api :as dr])\n\n(def my-ruleset (dr/ruleset {}))\n\n;; registering rules\n(dr/unirule my-ruleset\n  \"Matches iff `?person` lives in `?country`\"\n  '[(in-country ?person ?country)\n    [?person :person/address ?a]\n    [?a :address/town ?t]\n    [?t :town/country ?country]])\n    \n(dr/plurirule my-ruleset \n  \"Matches iff ?person said 'hello' in her native language\"\n  '(said-hello ?person))\n(dr/pluriimpl my-ruleset :english-hi\n  '[(said-hello ?person)\n    [?person :speaks :english]\n    [?person :said \"hi\"]])\n(dr/pluriimpl my-ruleset :french-hi\n  '[(said-hello ?person)\n    [?person :speaks :french]\n    [?person :said \"salut\"]])\n\n;; retrieving the rules, for use in query.\n(dr/rules my-ruleset)\n=\u003e [[(in-country ?person ?country)\n     [?person :person/address ?a]\n     [?a :address/town ?t]\n     [?t :town/country ?country]]\n    [(said-hello ?person)\n     [?person :speaks :english]\n     [?person :said \"hi\"]]\n    [(said-hello ?person)\n     [?person :speaks :french]\n     [?person :said \"salut\"]]]\n```\n\n### Declaring a ruleset\n\nFirst, you need to declare a *ruleset*, \n which is a store to which you will be able to register rules:\n\n```clojure\n(require '[datalog-rules.api :as dr])\n\n;; creating a ruleset (with default options)\n(def my-ruleset (dr/ruleset {}))\n```\n\n### Registering rules\n\nThen you can register Datalog rules to that ruleset.\n `datalog-rules` makes an explicit distinction between 2 sorts of rules:\n\n * *unirules*, for which only 1 implementation is registered (analoguous to Clojure functions)\n * *plurirules*, for which several named implementations are registered (analogous to Clojure multimethods) \n \n```clojure\n;; registering a unirule:\n(dr/unirule my-ruleset\n  \"Matches iff `?person` lives in `?country`\"\n  '[(in-country ?person ?country)\n    [?person :person/address ?a]\n    [?a :address/town ?t]\n    [?t :town/country ?country]])\n\n;; registering a plurirule\n; declaring the interface...\n(dr/plurirule my-ruleset \n  \"Matches iff ?person said 'hello' in her native language\"\n  '(said-hello ?person))\n; ... then registering implementations:\n(dr/pluriimpl my-ruleset :english-hi\n  '[(said-hello ?person)\n    [?person :speaks :english]\n    [?person :said \"hi\"]])\n(dr/pluriimpl my-ruleset :french-hi\n  '[(said-hello ?person)\n    [?person :speaks :french]\n    [?person :said \"salut\"]])\n```\n\nThe facts that a unirule has only 1 implementation, and that plurirule implementations are named, \n allow `unirule`, `plurirule` and `pluriimpl` calls to be idempotent - thus more friendly to interactive development.  \n\n### Using rules in query\n\nAfter having registered the rules, you can retrieve by calling `dr/rules`: \n\n```clojure\n(dr/rules my-ruleset)\n=\u003e [[(in-country ?person ?country)\n     [?person :person/address ?a]\n     [?a :address/town ?t]\n     [?t :town/country ?country]]\n    [(said-hello ?person)\n     [?person :speaks :english]\n     [?person :said \"hi\"]]\n    [(said-hello ?person)\n     [?person :speaks :french]\n     [?person :said \"salut\"]]]\n```\n\nYou can them readily use them in query:\n     \n```clojure\n(require '[datomic.api :as d])\n\n(defn who-said-hello-in-america [db]\n  (d/q '[:find [?person ...] :in % $ :where \n         (in-country ?person :usa)\n         (said-hello ?person)]\n    (dr/rules my-ruleset) db))\n```\n\n`dr/rules` caches its result such that subsequent calls on the same ruleset return identical data structures,\n which can be leveraged by the Datalog engine. \n\n### Docs and source inspection\n\nSimilarly to `clojure.repl/doc`, `datalog-rules.api/rule-doc` prints the documentation for a registered rule:\n\n```clojure\n(dr/rule-doc my-ruleset in-country)\n;-------------------------\n;(in-country ?person ?country)\n;\n;Matches iff `?person` lives in `?country`\n=\u003e nil\n```\n\nLikewise, `datalog-rules.api/rule-source` returns the source for a registered rule:\n\n```clojure \n(dr/rule-source my-ruleset said-hello)\n=\u003e\n[[(said-hello ?person) [?person :speaks :english] [?person :said \"hi\"]]\n [(said-hello ?person) [?person :speaks :french] [?person :said \"salut\"]]]\n\n```\n\n### Reversed rules generation (experimental)\n\nIn Datomic's current implementation of Datalog,\n the order of clauses in a rule [matters for performance](http://docs.datomic.com/query.html#sec-7).\n\nFor instance, the `(in-country ?person ?country)` rule we wrote above is fast if `?person` is already bound,\n but slow if `?country` is already bound and `?person` is not.\n\nIf that's an issue, one solution is to define 2 rules with different clauses order in their body,\n and choose which one to use depending on the query:\n\n```clojure\n(dr/unirule my-ruleset\n  \"Matches iff `?person` lives in `?country`. Binds ?person first.\"\n  '[(in-country ?person ?country)\n    [?person :person/address ?a]\n    [?a :address/town ?t]\n    [?t :town/country ?country]])\n(dr/unirule my-ruleset\n  \"Matches iff `?person` lives in `?country`. Binds ?country first.\"\n  '[(in-country- ?person ?country)\n    [?t :town/country ?country]\n    [?a :address/town ?t]\n    [?person :person/address ?a]])\n```\n\nOf course, the issue is that you're duplicating code when doing this.\n Instead, you can use the `:auto-reverse` option of `(datalog-rules.api/ruleset)`:\n\n```clojure\n(def my-ruleset (dr/ruleset {:auto-reverse true}))\n\n(dr/unirule my-ruleset\n  \"Matches iff `?person` lives in `?country`.\"\n  '[(in-country ?person ?country)\n    [?person :person/address ?a]\n    [?a :address/town ?t]\n    [?t :town/country ?country]])\n\n(dr/rules my-ruleset)\n=\u003e [[(in-country ?person ?country)\n     [?person :person/address ?a]\n     [?a :address/town ?t]\n     [?t :town/country ?country]]\n    [(in-country- ?person ?country) ;; for the reversed rule, a '-' is appended to the rule name\n     [?t :town/country ?country]\n     [?a :address/town ?t]\n     [?person :person/address ?a]]]\n\n```\n\n### Recommended code organisation\n\n#### Isolate the ruleset in one namespace (and don't reload it)\n\nSimilarly to `extend-protocol`, `unirule` / `plurirule` / `pluriimpl` work by performing load-time mutation of the\ntarget ruleset.\n\nTherefore, to avoid having problems during interactive development,\n it is recommended to isolate each ruleset in its own namespace:\n\n```clojure\n(ns myapp.ruleset\n  (:require [datalog-rules.api :as dr]))\n\n(def my-ruleset (dr/ruleset {})\n```\n\n#### Write your own wrappers\n\nMost applications probably need only one ruleset.\n To make things more comfortable, I recommend making your own wrappers the `datalog-rules` API:\n\n```clojure\n(ns myapp.rules\n  (:require [datalog-rules.api :as dr]\n            [myapp.ruleset :refer [my-ruleset]))\n\n(def unirule (partial dr/unirule my-ruleset))\n(def plurirule (partial dr/plurirule my-ruleset))\n(def pluriimpl (partial dr/pluriimpl my-ruleset))\n(defmacro rule-doc [rule-name]\n  `(dr/rule-doc my-ruleset ~rule-name))\n(defmacro rule-source [rule-name]\n  `(dr/rule-source my-ruleset ~rule-name))\n\n(defn rules []\n  (dr/rules my-ruleset))\n```\n\n## Function - Rule analogy\n\n| Functions | Rules |\n|-----------|-------|\n|`(defn ...)` | `(unirule my-ruleset ...)`|\n|`(defmulti \u003cname\u003e ...)` | `(plurirule my-ruleset ...)`|\n|`(defmethod \u003cname\u003e ...)` | `(pluriimpl my-ruleset ...)`|\n|`(doc \u003cname\u003e)` | `(rule-doc my-ruleset \u003cname\u003e)`|\n|`(source \u003cname\u003e)` | `(rule-source my-ruleset \u003cname\u003e)`|\n\n## Notes\n\nInterestingly, because Datalog is data-oriented, this library has no dependency to a concrete Datalog engine\n(such as Datomic Peer Library or DataScript).\n\n## TODO\n\n* tree shaking\n\n## License\n\nCopyright © 2016 Valentin Waeselynck and contributors.\n\nDistributed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvvvalvalval%2Fdatalog-rules","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvvvvalvalval%2Fdatalog-rules","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvvvalvalval%2Fdatalog-rules/lists"}