Commit | Line | Data |
---|---|---|
513810af | 1 | ;; equality.mal |
3e9b89d4 | 2 | |
513810af DM |
3 | ;; This file checks whether the `=` function correctly implements equality of |
4 | ;; hash-maps and sequences (lists and vectors). If not, it redefines the `=` | |
5 | ;; function with a pure mal (recursive) implementation that only relies on the | |
6 | ;; native original `=` function for comparing scalars (integers, booleans, | |
83665b4f | 7 | ;; symbols, strings, keywords, atoms, nil). |
513810af DM |
8 | |
9 | ;; Save the original (native) `=` as scalar-equal? | |
10 | (def! scalar-equal? =) | |
11 | ||
3e9b89d4 | 12 | ;; A faster `and` macro which doesn't use `=` internally. |
b1a8dbd5 | 13 | (defmacro! bool-and ; boolean |
bf6647fb NB |
14 | (fn* [& xs] ; interpreted as logical values |
15 | (if (empty? xs) | |
16 | true | |
b1a8dbd5 NB |
17 | `(if ~(first xs) (bool-and ~@(rest xs)) false)))) |
18 | (defmacro! bool-or ; boolean | |
bf6647fb NB |
19 | (fn* [& xs] ; interpreted as logical values |
20 | (if (empty? xs) | |
21 | false | |
b1a8dbd5 | 22 | `(if ~(first xs) true (bool-or ~@(rest xs)))))) |
3e9b89d4 | 23 | |
83665b4f | 24 | (def! starts-with? |
513810af | 25 | (fn* [a b] |
b1a8dbd5 NB |
26 | (bool-or (empty? a) |
27 | (bool-and (mal-equal? (first a) (first b)) | |
28 | (starts-with? (rest a) (rest b)))))) | |
513810af | 29 | |
513810af DM |
30 | (def! hash-map-vals-equal? |
31 | (fn* [a b map-keys] | |
b1a8dbd5 NB |
32 | (bool-or (empty? map-keys) |
33 | (let* [key (first map-keys)] | |
34 | (bool-and (contains? b key) | |
35 | (mal-equal? (get a key) (get b key)) | |
36 | (hash-map-vals-equal? a b (rest map-keys))))))) | |
513810af | 37 | |
513810af DM |
38 | ;; This implements = in pure mal (using only scalar-equal? as native impl) |
39 | (def! mal-equal? | |
40 | (fn* [a b] | |
41 | (cond | |
83665b4f NB |
42 | |
43 | (sequential? a) | |
b1a8dbd5 NB |
44 | (bool-and (sequential? b) |
45 | (scalar-equal? (count a) (count b)) | |
46 | (starts-with? a b)) | |
83665b4f NB |
47 | |
48 | (map? a) | |
49 | (let* [keys-a (keys a)] | |
b1a8dbd5 NB |
50 | (bool-and (map? b) |
51 | (scalar-equal? (count keys-a) (count (keys b))) | |
52 | (hash-map-vals-equal? a b keys-a))) | |
83665b4f NB |
53 | |
54 | true | |
55 | (scalar-equal? a b)))) | |
513810af DM |
56 | |
57 | (def! hash-map-equality-correct? | |
58 | (fn* [] | |
59 | (try* | |
b1a8dbd5 NB |
60 | (bool-and (= {:a 1} {:a 1}) |
61 | (not (= {:a 1} {:a 1 :b 2}))) | |
513810af DM |
62 | (catch* _ false)))) |
63 | ||
64 | (def! sequence-equality-correct? | |
65 | (fn* [] | |
66 | (try* | |
b1a8dbd5 NB |
67 | (bool-and (= [:a :b] (list :a :b)) |
68 | (not (= [:a :b] [:a :b :c]))) | |
513810af DM |
69 | (catch* _ false)))) |
70 | ||
71 | ;; If the native `=` implementation doesn't support sequences or hash-maps | |
72 | ;; correctly, replace it with the pure mal implementation | |
b1a8dbd5 NB |
73 | (if (not (bool-and (hash-map-equality-correct?) |
74 | (sequence-equality-correct?))) | |
513810af DM |
75 | (do |
76 | (def! = mal-equal?) | |
77 | (println "equality.mal: Replaced = with pure mal implementation"))) |