2015-11-17 22:41:06 +03:00
|
|
|
;; equality.mal
|
2019-05-15 06:05:26 +03:00
|
|
|
|
2015-11-17 22:41:06 +03:00
|
|
|
;; This file checks whether the `=` function correctly implements equality of
|
|
|
|
;; hash-maps and sequences (lists and vectors). If not, it redefines the `=`
|
|
|
|
;; function with a pure mal (recursive) implementation that only relies on the
|
|
|
|
;; native original `=` function for comparing scalars (integers, booleans,
|
2019-05-18 02:38:59 +03:00
|
|
|
;; symbols, strings, keywords, atoms, nil).
|
2015-11-17 22:41:06 +03:00
|
|
|
|
|
|
|
;; Save the original (native) `=` as scalar-equal?
|
|
|
|
(def! scalar-equal? =)
|
|
|
|
|
2019-05-15 06:05:26 +03:00
|
|
|
;; A faster `and` macro which doesn't use `=` internally.
|
2019-05-30 20:27:45 +03:00
|
|
|
(defmacro! bool-and ; boolean
|
2019-05-15 16:49:35 +03:00
|
|
|
(fn* [& xs] ; interpreted as logical values
|
|
|
|
(if (empty? xs)
|
|
|
|
true
|
2019-05-30 20:27:45 +03:00
|
|
|
`(if ~(first xs) (bool-and ~@(rest xs)) false))))
|
|
|
|
(defmacro! bool-or ; boolean
|
2019-05-15 16:49:35 +03:00
|
|
|
(fn* [& xs] ; interpreted as logical values
|
|
|
|
(if (empty? xs)
|
|
|
|
false
|
2019-05-30 20:27:45 +03:00
|
|
|
`(if ~(first xs) true (bool-or ~@(rest xs))))))
|
2019-05-15 06:05:26 +03:00
|
|
|
|
2019-05-18 02:38:59 +03:00
|
|
|
(def! starts-with?
|
2015-11-17 22:41:06 +03:00
|
|
|
(fn* [a b]
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-or (empty? a)
|
|
|
|
(bool-and (mal-equal? (first a) (first b))
|
|
|
|
(starts-with? (rest a) (rest b))))))
|
2015-11-17 22:41:06 +03:00
|
|
|
|
|
|
|
(def! hash-map-vals-equal?
|
|
|
|
(fn* [a b map-keys]
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-or (empty? map-keys)
|
|
|
|
(let* [key (first map-keys)]
|
|
|
|
(bool-and (contains? b key)
|
|
|
|
(mal-equal? (get a key) (get b key))
|
|
|
|
(hash-map-vals-equal? a b (rest map-keys)))))))
|
2015-11-17 22:41:06 +03:00
|
|
|
|
|
|
|
;; This implements = in pure mal (using only scalar-equal? as native impl)
|
|
|
|
(def! mal-equal?
|
|
|
|
(fn* [a b]
|
|
|
|
(cond
|
2019-05-18 02:38:59 +03:00
|
|
|
|
|
|
|
(sequential? a)
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-and (sequential? b)
|
|
|
|
(scalar-equal? (count a) (count b))
|
|
|
|
(starts-with? a b))
|
2019-05-18 02:38:59 +03:00
|
|
|
|
|
|
|
(map? a)
|
|
|
|
(let* [keys-a (keys a)]
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-and (map? b)
|
|
|
|
(scalar-equal? (count keys-a) (count (keys b)))
|
|
|
|
(hash-map-vals-equal? a b keys-a)))
|
2019-05-18 02:38:59 +03:00
|
|
|
|
|
|
|
true
|
|
|
|
(scalar-equal? a b))))
|
2015-11-17 22:41:06 +03:00
|
|
|
|
|
|
|
(def! hash-map-equality-correct?
|
|
|
|
(fn* []
|
|
|
|
(try*
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-and (= {:a 1} {:a 1})
|
|
|
|
(not (= {:a 1} {:a 1 :b 2})))
|
2015-11-17 22:41:06 +03:00
|
|
|
(catch* _ false))))
|
|
|
|
|
|
|
|
(def! sequence-equality-correct?
|
|
|
|
(fn* []
|
|
|
|
(try*
|
2019-05-30 20:27:45 +03:00
|
|
|
(bool-and (= [:a :b] (list :a :b))
|
|
|
|
(not (= [:a :b] [:a :b :c])))
|
2015-11-17 22:41:06 +03:00
|
|
|
(catch* _ false))))
|
|
|
|
|
|
|
|
;; If the native `=` implementation doesn't support sequences or hash-maps
|
|
|
|
;; correctly, replace it with the pure mal implementation
|
2019-05-30 20:27:45 +03:00
|
|
|
(if (not (bool-and (hash-map-equality-correct?)
|
|
|
|
(sequence-equality-correct?)))
|
2015-11-17 22:41:06 +03:00
|
|
|
(do
|
|
|
|
(def! = mal-equal?)
|
|
|
|
(println "equality.mal: Replaced = with pure mal implementation")))
|