Created
March 18, 2022 21:21
-
-
Save lynaghk/b9a769aff493c6fb04d7497da5a83cab to your computer and use it in GitHub Desktop.
Rel and Datascript comparison
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; A comparison of Rel: https://docs.relational.ai/rel/primer/aggregations_groupby_joins | |
;; with datascript: https://github.com/tonsky/datascript/ | |
;; Unless otherwise indicated, all datascript outputs match the ones shown in the Rel Primer. | |
(ns user | |
(:require [datascript.core :as d] | |
clojure.string)) | |
(def csv | |
"Messi,70,32,BFC,Argentina | |
Griezmann,45,28,BFC,France | |
Busquets,15,31,BFC,Spain | |
Pique,12,32,BFC,Spain | |
Dembele,12,22,BFC,France | |
Umtiti,12,25,BFC,France | |
Cortois,7,28,RM,Belgium | |
Carvajal,7,28,RM,Spain | |
Ramos,15,34,RM,Spain | |
Varane,7,27,RM,France | |
Marcelo,7,32,RM,Brazil | |
Kroos,10,30,RM,Germany | |
Modric,10,35,RM,Croatia") | |
(def tuples | |
(->> (clojure.string/split csv #"\n") | |
(map (fn [line] | |
(clojure.string/split line #","))) | |
(mapcat (fn [[name salary age plays-for nationality]] | |
[[name :name name] | |
[name :salary (parse-long salary)] | |
[name :age (parse-long age)] | |
[name :plays-for plays-for] | |
[name :nationality nationality]])))) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output = { (:sum, sum[salary]); | |
;; (:average, average[salary]); | |
;; (:count, count[salary]); | |
;; (:argmax, argmax[salary]) } | |
(d/q '{:find [(sum ?salary) (avg ?salary) (count ?salary)] | |
:with [?player] ;;see Rel primer discussion of sum equal to 13 vs. 6 | |
:where [[?player :salary ?salary]]} | |
tuples) | |
;;Datascript doesn't have argmin/argmax, so that has to be constructed manually. | |
;;See discussion in Clojure Slack: https://app.slack.com/client/T03RZGPFR/C07V8N22C/thread/C07V8N22C-1647624999.423469 | |
(defn arg-f | |
[f] | |
(fn [tuples] | |
(let [v (reduce f (map last tuples))] | |
(keep (fn [tuple] | |
(when (= v (last tuple)) | |
(vec (butlast tuple)))) | |
tuples)))) | |
(def argmin (arg-f min)) | |
(def argmax (arg-f max)) | |
(d/q '{:find [(user/argmax ?tuple)] | |
:where [[?player :salary ?salary] | |
[(vector ?player ?salary) ?tuple]]} | |
tuples) | |
;; => ([(["Messi"])]) | |
;;Note that the datascript output isn't a proper relation --- it has nested structure rather than just being tuples. | |
;;This is more apparent when we use argmin and argmax together with additional groupings: | |
(d/q '{:find [?even (user/argmin ?tuple) (user/argmax ?tuple)] | |
:where [[?player :salary ?salary] | |
[(even? ?salary) ?even] | |
[(vector ?player ?salary) ?tuple]]} | |
[["A" :salary 10] | |
["B" :salary 7] | |
["C" :salary 5] | |
["D" :salary 12] | |
["E" :salary 12]]) | |
;; => ([true (["A"]) (["D"] ["E"])] | |
;; [false (["C"]) (["B"] )]) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output = c in {"RM"; "BFC"; "Chelsea"} : | |
;; sum[p where plays_for(p, c) : salary[p] ] <++ 0 | |
(d/q '{:find [?team (sum ?salary)] | |
:with [?player] | |
:in [$ [?team ...]] | |
:where [(or (and [(ground 0) ?salary] | |
[(ground 0) ?player]) ;;need a fake "player" since it's used by :with clause | |
(and [?player :salary ?salary] | |
[?player :plays-for ?team]))]} | |
tuples ["RM" "BFC" "Chelsea"]) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def mydomain = range[1, 5, 1] | |
;; def output = x-y, x+y, x*y for x in mydomain, y in mydomain where x+y = 5 | |
(d/q '{:find [?x ?y ?diff ?sum ?product] | |
:where [[?x] | |
[?y] | |
[(- ?x ?y) ?diff] | |
[(+ ?x ?y) ?sum] | |
[(* ?x ?y) ?product] | |
[(= ?sum 5)]]} | |
(map vector (range 5))) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output = x in team : average[p where plays_for(p, x): age[p] ] | |
(d/q '{:find [?team (avg ?age)] | |
:with [?player] | |
:where [[?player :plays-for ?team] | |
[?player :age ?age]]} | |
tuples) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output[a] = average[p where age(p, a) : salary[p]], count[p : age(p, a)] | |
(d/q '{:find [?age (avg ?salary) (count ?age)] ;;variable passed to count doesn't matter | |
:with [?player] | |
:where [[?player :salary ?salary] | |
[?player :age ?age]]} | |
tuples) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output(player, team, country) = plays_for(player, team) and | |
;; nationality(player, country) | |
(d/q '{:find [?player ?team ?country] | |
:where [[?player :plays-for ?team] | |
[?player :nationality ?country]]} | |
tuples) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def output(team, country) = plays_for(player, team) and | |
;; nationality(player, country) from player | |
(d/q '{:find [?team ?country] | |
:where [[?player :plays-for ?team] | |
[?player :nationality ?country]]} | |
tuples) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; def myrel = { (1, "a", 2); (1, "b", 3); (3, "a", 6); (4, "b", 12) } | |
;; def output(x) = myrel(_, x, _) | |
(d/q '{:find [?x] | |
:where [[_ ?x _]]} | |
[[1 "a" 2] [1 "b" 3] [3 "a" 6] [4 "b" 12]]) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment