Skip to content

Instantly share code, notes, and snippets.

@lynaghk
Created March 18, 2022 21:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lynaghk/b9a769aff493c6fb04d7497da5a83cab to your computer and use it in GitHub Desktop.
Save lynaghk/b9a769aff493c6fb04d7497da5a83cab to your computer and use it in GitHub Desktop.
Rel and Datascript comparison
;; 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