1
1
mirror of https://github.com/kanaka/mal.git synced 2024-10-27 22:58:00 +03:00
mal/lib/equality.mal

79 lines
2.6 KiB
Plaintext
Raw Normal View History

;; equality.mal
;; 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,
;; symbols, strings).
;; Save the original (native) `=` as scalar-equal?
(def! scalar-equal? =)
;; A faster `and` macro which doesn't use `=` internally.
(defmacro! and2 ; `true` or `nil`
(fn* [& xs] ; interpreted as logical values
(if (empty? xs)
true
`(if ~(first xs) (and2 ~(rest xs))))))
(defmacro! ord2 ; `true` or `false`
(fn* [& xs] ; interpreted as logical values
(if (empty? xs)
false
`(if ~(first xs) true (ord2 ~(rest xs))))))
;; Implement `=` for two sequential arguments
(def! sequential-equal?
(fn* [a b]
(and2 (scalar-equal? (count a) (count b))
(or2 (empty? a)
(and2 (mal-equal? (first a) (first b))
(sequential-equal? (rest a) (rest b)))))))
;; Helper function
(def! hash-map-vals-equal?
(fn* [a b map-keys]
(or2 (scalar-equal? 0 (count map-keys))
(let* [key (first map-keys)]
(and2 (contains? a key)
(contains? b key)
(mal-equal? (get a key) (get b key))
(hash-map-vals-equal? a b (rest map-keys)))))))
;; Implement `=` for two hash-maps
(def! hash-map-equal?
(fn* [a b]
(let* [keys-a (keys a)]
(and2 (scalar-equal? (count keys-a) (count (keys b)))
(hash-map-vals-equal? a b keys-a)))))
;; This implements = in pure mal (using only scalar-equal? as native impl)
(def! mal-equal?
(fn* [a b]
(cond
(and2 (sequential? a) (sequential? b)) (sequential-equal? a b)
(and2 (map? a) (map? b)) (hash-map-equal? a b)
true (scalar-equal? a b))))
(def! hash-map-equality-correct?
(fn* []
(try*
(and2 (= {:a 1} {:a 1})
(not (= {:a 1} {:a 1 :b 2})))
(catch* _ false))))
(def! sequence-equality-correct?
(fn* []
(try*
(and2 (= [:a :b] (list :a :b))
(not (= [:a :b] [:a :b :c])))
(catch* _ false))))
;; If the native `=` implementation doesn't support sequences or hash-maps
;; correctly, replace it with the pure mal implementation
(if (not (and2 (hash-map-equality-correct?) (sequence-equality-correct?)))
(do
(def! = mal-equal?)
(println "equality.mal: Replaced = with pure mal implementation")))
nil