-
Notifications
You must be signed in to change notification settings - Fork 110
/
platform.cljc
115 lines (106 loc) · 5.1 KB
/
platform.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
(ns clara.rules.platform
"This namespace is for internal use and may move in the future.
Platform unified code Clojure/ClojureScript.")
(defn throw-error
"Throw an error with the given description string."
[^String description]
(throw #?(:clj (IllegalArgumentException. description) :cljs (js/Error. description))))
(defn query-param
"Coerces a query param to a parameter keyword such as :?param, if an unsupported type is
supplied then an exception will be thrown"
[p]
(cond
(keyword? p) p
(symbol? p) (keyword p)
:else
(throw-error (str "Query bindings must be specified as a keyword or symbol: " p))))
;; This class wraps Clojure objects to ensure Clojure's equality and hash
;; semantics are visible to Java code. This allows these Clojure objects
;; to be safely used in things like Java Sets or Maps.
;; This class also accepts and stores the hash code, since it almost always
;; will be used once and generally more than once.
#?(:clj
(deftype JavaEqualityWrapper [wrapped ^int hash-code]
Object
(equals [this other]
(and (instance? JavaEqualityWrapper other)
(= wrapped (.wrapped ^JavaEqualityWrapper other))))
(hashCode [this]
hash-code)))
#?(:clj
(defn group-by-seq
"Groups the items of the given coll by f to each item. Returns a seq of tuples of the form
[f-val xs] where xs are items from the coll and f-val is the result of applying f to any of
those xs. Each x in xs has the same value (f x). xs will be in the same order as they were
found in coll.
The behavior is similar to calling `(seq (group-by f coll))` However, the returned seq will
always have consistent ordering from process to process. The ordering is insertion order
as new (f x) values are found traversing the given coll collection in its seq order. The
returned order is made consistent to ensure that relevant places within the rules engine that
use this grouping logic have deterministic behavior across different processes."
[f coll]
(let [^java.util.Map m (reduce (fn [^java.util.Map m x]
(let [k (f x)
;; Use Java's hashcode for performance reasons as
;; discussed at https://github.com/cerner/clara-rules/issues/393
wrapper (JavaEqualityWrapper. k
(if (nil? k)
(int 0)
(int (.hashCode ^Object k))))
xs (or (.get m wrapper)
(transient []))]
(.put m wrapper (conj! xs x)))
m)
(java.util.LinkedHashMap.)
coll)
it (.iterator (.entrySet m))]
;; Explicitly iterate over a Java iterator in order to avoid running into issues as
;; discussed in http://dev.clojure.org/jira/browse/CLJ-1738
(loop [coll (transient [])]
(if (.hasNext it)
(let [^java.util.Map$Entry e (.next it)]
(recur (conj! coll [(.wrapped ^JavaEqualityWrapper (.getKey e)) (persistent! (.getValue e))])))
(persistent! coll)))))
:cljs
(def group-by-seq (comp seq clojure.core/group-by)))
#?(:clj
(defn tuned-group-by
"Equivalent of the built-in group-by, but tuned for when there are many values per key."
[f coll]
(->> coll
(reduce (fn [map value]
(let [k (f value)
items (or (.get ^java.util.HashMap map k)
(transient []))]
(.put ^java.util.HashMap map k (conj! items value)))
map)
(java.util.HashMap.))
(reduce (fn [map [key value]]
(assoc! map key (persistent! value)))
(transient {}))
(persistent!)))
:cljs
(def tuned-group-by clojure.core/group-by))
#?(:clj
(defmacro thread-local-binding
"Wraps given body in a try block, where it sets each given ThreadLocal binding
and removes it in finally block."
[bindings & body]
(when-not (vector? bindings)
(throw (ex-info "Binding needs to be a vector."
{:bindings bindings})))
(when-not (even? (count bindings))
(throw (ex-info "Needs an even number of forms in binding vector"
{:bindings bindings})))
(let [binding-pairs (partition 2 bindings)]
`(try
~@(for [[tl v] binding-pairs]
`(.set ~tl ~v))
~@body
(finally
~@(for [[tl] binding-pairs]
`(.remove ~tl)))))))
(defmacro eager-for
"A for wrapped with a doall to force realisation. Usage is the same as regular for."
[& body]
`(doall (for ~@body)))