Search code examples
clojure

Evaluation with map, apply of function, and vector of quoted expressions


What is the correct way to think about the following?

user=> (map #(apply println %) ['(+ 1 1)])
+ 1 1
;=> (nil)

I have in my head that this should possibly print 2 and not what it does print. I have a notion that the quoted '(+ 1 1) should be evaluated once to be the data (+ 1 1) when it is inside of [], and again (+ 1 1) should be evaluated to 2 when apply puts it in the argument position of println.

Here is how I am kind of picturing evaluation:

(map #(apply println %) ['(+ 1 1)])
; --> to reader
(<map-function-var> <anonymous-println-function-var> [(+ 1 1)])
; --> <map-function-var> applys the <anonymous-println-function-var> to the args:
[(#(apply println %) [(+ 1 1)])]
; --> apply the argument
[(println (+ 1 1)]
; --> eval it
[(println 2]
; --> print it
2
;=> nil

My mental model obviously has one too many evaluations in it, and I think the last one, where 2 is produced, is where it is wrong, but I am not sure. Can someone explain how I should be thinking of this sequence of substitutions and evaluations?

Update: it may have something to do with this?

user=> (map #(apply println %) [(list "a" "b") (list "c" "d")])
a b
c d
(nil nil)

Once the contents of [] are evaluated, they are now data, and when they are passed to #(apply println %), they are considered data, as the elements of [] have been evaluated, and will not be passed to the evaluator to try and find the function "a" or "c"... ?


Solution

  • First of all, map returns a lazy sequence and you shouldn't use it for side effects (like printing).

    There are ways to force all side effects in the lazy sequence (dorun, doall), but in this case, you can directly use run! or doseq.

    As for the main problem: (+ 1 1) is just the way how REPL prints the result of the expression '(+ 1 1) (a result of that expression is a list with one symbol and two numbers). But if you just copy-paste that printed representation into REPL, you will get Clojure expression evaluating to 2.

    Following Evaluation reference article, I will try to describe what is going on when you call (map #(apply println %) ['(+ 1 1)]):

    1. map is evaluated => #object[clojure.core$map]
    2. #(apply println %) is evaluated => #object[fn]
    3. ['(+ 1 1)] is evaluated => [(+ 1 1)] (printed representation of vector with list with one symbol and two numbers)
    4. #object[clojure.core$map] is called with #object[fn] and [(+ 1 1)]

    Now, you could look at (source map) to see, what exactly map does with its arguments (laziness, chunking...), but for the sake of simplicity, let's focus just on this small expression from the implementation:

    (f (first s))
    
    1. f is evaluated => #object[fn]
    2. first is evaluated => #object[clojure.core$first]
    3. s is evaluated => [(+ 1 1)]
    4. #object[clojure.core$first] is called with [(+ 1 1)] => (+ 1 1)
    5. #object[fn] (with body (apply println %)) is called with (+ 1 1)
      1. apply is evaluated => #object[clojure.core$apply]
      2. println is evaluated => #object[clojure.core$println]
      3. % is evaluated => (+ 1 1)
      4. #object[clojure.core$apply] is called with #object[clojure.core$println] and (+ 1 1)

    You could now look at (source apply)- just the relevant part:

    ([^clojure.lang.IFn f args]
         (. f (applyTo (seq args))))
    

    That is some Java interop, but as part of the evaluation, args evaluate to (+ 1 1).

    What is important: (#(apply println %) '(+ 1 1)) is equivalent to (apply println '(+ 1 1)) and that is equivalent to (println '+ '1 '1) and (println '+ 1 1).

    My point is- during evaluation, the expression (+ 1 1) is never evaluated (so you won't get 2 as the result), but some symbols and expressions are evaluated to (+ 1 1).