1
votes

I want to write a specification for what it means for a function to be a predicate. There seems to be three ways to go about what a predicate is in the Clojure world, though most seem to agree that they should end in a question mark.

  1. A function which takes one argument and returns true or false.
  2. A function which takes one argument and returns true, false or nil.
  3. A function which takes one argument and returns a truthey or falsey value.

Jira Ticket on what Predicate is.

EDIT: A predicate can also take multiple arguments as exemplified by contains?.

2
Keep it simple; stick to (1)!Alan Thompson

2 Answers

4
votes

I think the most accurate spec for a predicate is:

(s/fspec :args (s/cat :v any?) :ret any?)

While predicates generally return true/false, there is no requirement for them to do so - the only required contract is that it takes one value and returns a value which will be treated as a logical truth value.

2
votes

If I've understood clojure.spec/fdef correct it allows us to make specifications like the described in the question.

(spec/fdef ::predicate-1
           :args (spec/cat :arg any?)
           :ret  boolean?)

Which we can test by passing it some examples which we know should pass or fail the test:

(spec/valid? ::predicate-1 boolean?)       => true
(spec/valid? ::predicate-1 (fn [a] 5))     => false
(spec/valid? ::predicate-1 (fn [a] true))  => true
(spec/valid? ::predicate-1 (fn [a b] true))=> false
(spec/valid? ::predicate-1 #(= 10 %))      => true
(spec/valid? ::predicate-1 (fn [a] nil))   => false

For defenition nr. 2:

(spec/fdef ::predicate-2
           :args (spec/cat :arg any?)
           :ret  (spec/nilable boolean?))

(spec/valid? ::predicate-2 (fn [a] nil))   => true

And for nr. 3 any function which takes one argument is valid since everything in clojure is either truthy or falsey.

(spec/fdef ::predicate-3
           :args (spec/cat :arg any?)
           :ret  any?)

(spec/valid? ::predicate-3 identity)       => true
(spec/valid? ::predicate-3 str)            => true

One interesting thing we then seem to be able to do is have spec generate such functions for us:

(let [p (gen/generate (spec/gen ::pedicate-1))]
  (clojure.string/join
   " " [(p 0) (p 1) (p -1) (p nil) (p 'a) (p :a) (p (fn [a] a))]))
=> "false true true false true false false"

And out of that we can perhaps try to guess what the generated function does. But without being able to see the source we'll have a hard time checking whether our guess was correct or not.