I really like that you focus on the mathematical properties. That's where property-based testing really shines. Just like unit tests help us write testable code, property-based tests help us write operations with simple and useful properties.
A couple of comments:
a = a * 1 = a * 1 * 1 is idempotent. 1 is also the identity of *, which is another property.
Idempotence (function f is idempotent): f(a) = f(f(a))
a + 0 = a
a * 1 = a
usernames.append([]) = usernames
hashmap.merge({}) = hashmap
Bool1 && True = Bool1
There's also the mathematical idea of zero. a * 0 = 0. It's another cool property.
Zero (z is the zero of f): f(a, z) = z
intersect(set1, EmptySet) = EmptySet
Bool1 && False = False
I think the example you gave for associativity (with sorting and filtering) is actually an example of commutativity since it shows that order doesn't matter between sorting and filtering.
Math.max(a, b) = Math.max(b, a) (though this one is also associative :)
Bool1 && Bool2 = Bool2 && Bool1 (this one is associative too)
average(a, b) = average(b, a)
First off, I geeked out again when I saw I had a comment from you. I've loved several of your articles on Clojure!
Secondly, I think your critiques are correct. I misused idempotency slightly, and my associative example is actually commutative. I definitely appreciate the clarification and additional examples? Would you mind if I added those to the article?
Thanks again for the complements and clarifications, and keep up the good work at PurelyFunctional.tv!
Hey Jason,
Very nice article!
I really like that you focus on the mathematical properties. That's where property-based testing really shines. Just like unit tests help us write testable code, property-based tests help us write operations with simple and useful properties.
A couple of comments:
a = a * 1 = a * 1 * 1 is idempotent. 1 is also the identity of *, which is another property.
Idempotence (function f is idempotent): f(a) = f(f(a))
Examples:
Math.abs(x) = Math.abs(Math.abs(x))
hashmap.merge({a:1}) = hashmap.merge({a:1}).merge({a:1})
Identity (i is the identity of f): f(a, i) = a
Examples:
a + 0 = a
a * 1 = a
usernames.append([]) = usernames
hashmap.merge({}) = hashmap
Bool1 && True = Bool1
There's also the mathematical idea of zero. a * 0 = 0. It's another cool property.
Zero (z is the zero of f): f(a, z) = z
intersect(set1, EmptySet) = EmptySet
Bool1 && False = False
I think the example you gave for associativity (with sorting and filtering) is actually an example of commutativity since it shows that order doesn't matter between sorting and filtering.
Associativity examples:
hashmap1.merge(hashmap2.merge(hashmap3)) = (hasmap1.merge(hashmap2)).merge(hashmap3)
list1.append(list2.append(list3)) = (list1.append(list2)).append(list3)
Commutativity examples:
Math.max(a, b) = Math.max(b, a) (though this one is also associative :)
Bool1 && Bool2 = Bool2 && Bool1 (this one is associative too)
average(a, b) = average(b, a)
Again, this was an awesome article!
Rock on!
Eric
Hey Eric,
First off, I geeked out again when I saw I had a comment from you. I've loved several of your articles on Clojure!
Secondly, I think your critiques are correct. I misused idempotency slightly, and my associative example is actually commutative. I definitely appreciate the clarification and additional examples? Would you mind if I added those to the article?
Thanks again for the complements and clarifications, and keep up the good work at PurelyFunctional.tv!
Hey Jason,
Sorry I didn't reply to this sooner. Be my guest and reuse the examples.
Rock on!
Eric