Skip to content

Instantly share code, notes, and snippets.

@madstap
Created September 21, 2017 20:17
Show Gist options
  • Save madstap/47cc84aa9638c477dfc8e6ea1300d28b to your computer and use it in GitHub Desktop.
Save madstap/47cc84aa9638c477dfc8e6ea1300d28b to your computer and use it in GitHub Desktop.
(ns foo.bool-diff
"Have you ever wondered \"Can I write this boolean expression like that instead?\"
Well, I have. So I wrote this to verify whether I'm right.
Say I've got the expression:
(and (not (foo? x))
(not (bar? y)))
I might think \"Isn't that the same as negating an or?\"
(not (or (foo? x)
(bar? y)))
And in this simple case it's not very difficult to see that, yes, it is.
But I'm kinda dumb, so I'll lose time trying different combinations of
booleans in my head. I've even changed a working boolean thingy into a broken
one by mistake before.
Computers, however, are good a trying every combination of something.
So, are the two expressions above equivalent?
(diff-bool-exprs
(and (not :bool/foo)
(not :bool/bar))
(not (or :bool/foo
:bool/bar))) ;=> #{}
It returned an empty set. That means yes, they are.
diff-bool-exprs will substitute keywords with the namespace bool with every
possible combination of booleans, and return a set of pairs of the
expressions that have a different outcome.
If I try it with some expressions that aren't equivalent, it'll return a set
of pairs with the booleans filled in, so I can see in which cases
the expressions differ.
(diff-bool-exprs
(and :bool/foo :bool/bar)
(or (not :bool/foo)
:bool/bar))
;;=> #{[(and false false) (or (not false) false)]
[(and false true) (or (not false) true)]}"
(:require
[madstap.comfy :as comfy]
[clojure.walk :as walk]
[clojure.math.combinatorics :as combo]))
(comment
:dependencies [[org.clojure/clojure "1.9.0-beta1"]
[org.clojure/math.combinatorics "0.1.4"]
[madstap/comfy "1.0.2"]])
(defn placeholder? [x]
(and (keyword? x) (= "bool" (namespace x))))
(defn- get-bools [form]
(comfy/postwalk-transduce (filter placeholder?) conj form))
(defn- all-bool-combos [coll]
(->> coll
(map (fn [x]
[[x true] [x false]]))
(apply combo/cartesian-product)
(map (partial into {}))))
(defn diff-bool-exprs*
[a b]
(set (for [c (all-bool-combos (distinct (concat (get-bools a) (get-bools b))))
:let [[a' b'] (map (partial walk/postwalk-replace c) [a b])]
:when (not= (eval a') (eval b'))]
[a' b'])))
(defmacro diff-bool-exprs
"Takes two forms with keywords with the namespace bool as placeholders
for booleans and returns the set of combinations where they're not equivalent.
(By brute-force, trying all combinations and eval'ing them,
so don't use too many different placeholders.)"
[a b]
`(diff-bool-exprs* '~a '~b))
(comment
(= (set (all-bool-combos [:bool/a :bool/b]))
#{{:bool/a true :bool/b true}
{:bool/a false :bool/b true}
{:bool/a true :bool/b false}
{:bool/a false :bool/b false}})
(diff-bool-exprs
(and (not :bool/a)
(not :bool/b))
(not (or :bool/a
:bool/b))) ;=> #{}
(diff-bool-exprs
(not (and :bool/a
:bool/b))
(or (not :bool/a)
(not :bool/b))) ;=> #{}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment