Created
September 21, 2017 20:17
-
-
Save madstap/47cc84aa9638c477dfc8e6ea1300d28b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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