Search code examples
clojure

Why do these two functions in Clojure have different output?


Why those two functions have a different return?

(filter (and #(= (rem % 2) 1)
             #(= (rem % 3) 0)) 
          (take 100 (iterate inc 0)))  ;=> (0 3 6 9 12... 

(filter #(and (= (rem % 2) 1) 
              (= (rem % 3) 0)) 
          (take 100 (iterate inc 0)))  ;=> (3 9 15 21...

Is the form with two anonymous functions valid?


Solution

  • Expanding on the other answer...

    You have accidentally discovered a "feature" of Clojure which, IMHO, causes more trouble than it is worth.

    What is "truthy" in Clojure?

    Consider what Clojure considers "truthy":

    (defn truthy?
      [x]
      (if x
        :yes
        :no))
    
    (dotest
      (is= :yes (truthy? true))
      (is= :yes (truthy? 1))
      (is= :yes (truthy? :a))
    
      (is= :no (truthy? nil))
      (is= :no (truthy? false)))
    

    Note that a function object is also "truthy":

    (dotest 
      (let [fn-obj-1 (fn [x] (+ x 1)) 
            fn-obj-2        #(+ % 1)]  ; shorthand, equiv to fn-obj-1
        (is= :yes (truthy? fn-obj-1))
        (is= :yes (truthy? fn-obj-2))
        (is= 42 (fn-obj-1 41))  ; call the fn
        (is= 42 (fn-obj-2 41))  ; call the fn
      ))
    

    The "tricky" result of the and macro

    The and macro (source code) doesn't just return a boolean value as you may expect. Instead, it returns the item that "caused" the result:

    (dotest 
      (is= 3     (and 1 2 3))      ; 1
      (is= :a    (and 99 :a))      ; 2
    
      (is= nil   (and nil :a))     ; 3
      (is= false (and false 88)))  ; 4
    

    So if all the items are "truthy", and returns the last one since it is the "deciding factor" (cases 1 & 2). If one or more "falsey" values are present, and returns the first one found since it is the "deciding factor" (cases 3 & 4).

    Personally, I never use this feature of Clojure. IMHO it is something of a "trick" that makes code difficult to decipher correctly.

    How does the and macro work?

    In order to return the "deciding factor" to you, the and macro expands into code like this:

    ; (and 99 :a)
    (let [x 99]
      (if x 
        ; recursive invocation: (and :a)
        x))
    

    So if the 99 had been false or nil instead, it would have been returned to you. Since 99 is truthy, and recurses with the next value :a as follows:

    ; (and :a)
    :a
    

    Since there is only 1 item in this recursion level, it must be the "deciding factor" and the and macro just returns the raw value to you.

    Remember that and is a macro

    This means it runs at compile time (every macro is properly regarded as a "compiler extension"). In your first case, your code looks like so:

    (filter (and f1 f2) ...)
    

    where f1 and f2 are both functions. Since both are "truthy?", and returns the last item to you. Since the above code rewriting with let etc occurs at compile time, your code gets converted into:

    (filter 
      (let [x f1]
        (if x 
          f2
          f1))
      ...)
    

    And since f1 is truthy, that simplifies to

    (filter 
      f2
      ...)
    

    where f2 is just #(= (rem % 3) 0). So we see [0 3 6 ...] returned.


    Final Answer

    Because of a Clojure "quirk", your first filter does not generate a compiler error (although I think it should). The 2nd form does what you want, since filter expects exactly 1 function to use in deciding which items are retained.


    The above is based on this template project.