tests: make throw of non-strings optional/soft.
[jackhill/mal.git] / examples / equality.mal
1 ;;
2 ;; equality.mal
3 ;;
4 ;; This file checks whether the `=` function correctly implements equality of
5 ;; hash-maps and sequences (lists and vectors). If not, it redefines the `=`
6 ;; function with a pure mal (recursive) implementation that only relies on the
7 ;; native original `=` function for comparing scalars (integers, booleans,
8 ;; symbols, strings).
9 ;;
10
11 ;; Save the original (native) `=` as scalar-equal?
12 (def! scalar-equal? =)
13
14 ;; A simple `and` macro for two argument which doesn't use `=` internally
15 (defmacro! and2
16 (fn* [a b]
17 `(let* (and2_FIXME ~a)
18 (if and2_FIXME ~b and2_FIXME))))
19
20 ;; Implement `=` for two sequential arguments
21 (def! sequential-equal?
22 (fn* [a b]
23 (if (scalar-equal? (count a) (count b))
24 (if (empty? a)
25 true
26 (if (mal-equal? (first a) (first b))
27 (sequential-equal? (rest a) (rest b))
28 false))
29 false)))
30
31 ;; Helper function
32 (def! hash-map-vals-equal?
33 (fn* [a b map-keys]
34 (if (scalar-equal? 0 (count map-keys))
35 true
36 (let* [key (first map-keys)]
37 (if (and2
38 (and2 (contains? a key) (contains? b key))
39 (mal-equal? (get a key) (get b key)))
40 (hash-map-vals-equal? a b (rest map-keys))
41 false)))))
42
43 ;; Implement `=` for two hash-maps
44 (def! hash-map-equal?
45 (fn* [a b]
46 (let* [keys-a (keys a)]
47 (if (scalar-equal? (count keys-a) (count (keys b)))
48 (hash-map-vals-equal? a b keys-a)
49 false))))
50
51 ;; This implements = in pure mal (using only scalar-equal? as native impl)
52 (def! mal-equal?
53 (fn* [a b]
54 (cond
55 (and2 (sequential? a) (sequential? b)) (sequential-equal? a b)
56 (and2 (map? a) (map? b)) (hash-map-equal? a b)
57 true (scalar-equal? a b))))
58
59 (def! hash-map-equality-correct?
60 (fn* []
61 (try*
62 (and2 (= {:a 1} {:a 1})
63 (not (= {:a 1} {:a 1 :b 2})))
64 (catch* _ false))))
65
66 (def! sequence-equality-correct?
67 (fn* []
68 (try*
69 (and2 (= [:a :b] (list :a :b))
70 (not (= [:a :b] [:a :b :c])))
71 (catch* _ false))))
72
73 ;; If the native `=` implementation doesn't support sequences or hash-maps
74 ;; correctly, replace it with the pure mal implementation
75 (if (not (and2 (hash-map-equality-correct?) (sequence-equality-correct?)))
76 (do
77 (def! = mal-equal?)
78 (println "equality.mal: Replaced = with pure mal implementation")))