Sunday, July 19, 2015

Why reverse in my-partial

I'm currently going through the site Clojure for the Brave and True, which I find an excellent resource for getting acquainted with the language.

Chapter 4 describes a possible implementation of the partial function:


(defn my-partial
 [partialized-fn & args]
 (fn [& more-args]
 (apply partialized-fn (into more-args (reverse args)))))

The question I asked myself was: why do we need to reverse the args?
Let's examine that!

First we create a function, to which we will apply my-partial later:

(defn test1
  [arg1 arg2 arg3 arg4]
  (println (str "arg1='" arg1 "' arg2='" arg2 "' arg3='" arg3 "' arg4='" arg4 "'")))

Now, let's apply my-partial to test1:
(def test1-partial (my-partial test1 "arg1" "arg2"))
(test1-partial "arg3" "arg4")

We get the expected output, i.e.:
arg1='arg1' arg2='arg2' arg3='arg3' arg4='arg4'
nil 

The key is the 'into' function call:
(into '("arg3" "arg4") (reverse '("arg1" "arg2")))
Which returns:
("arg1" "arg2" "arg3" "arg4")

When we try to apply the list to the test1 function, we get the expected result:
(apply test1 (into '("arg3" "arg4") (reverse '("arg1" "arg2"))))
With the output:
arg1='arg1' arg2='arg2' arg3='arg3' arg4='arg4'
nil

Note, that vectors follow a different order when using into:
(into ["arg3" "arg4"] (reverse ["arg1" "arg2"]))
Result:
["arg3" "arg4" "arg2" "arg1"]

Indeed, when using a vector, the parameters are not passed in the expected order.
(apply test1 (into ["arg3" "arg4"] (reverse ["arg1" "arg2"])))
Result:
arg1='arg3' arg2='arg4' arg3='arg2' arg4='arg1'
nil

The different behavior is reported in an example of the documentation of into:
; Items are conj'ed one at a time, which puts them at the head of 
; the destination list
user=> (into () '(1 2 3))
(3 2 1)

; This does not happen for a vector, however, due to the behavior of conj:
user=> (into [1 2 3] '(4 5 6))
[1 2 3 4 5 6]

The reason for this difference is explained in a stack-overlow post. According to the linked answers, lists in clojure are implemented as linked-lists, thus it is more efficient to add new elements at their beginning. In contrast, for vectors it is easier to add an element at the end: in this case, there is no need to shift the elements of the vector. In fact, if extra empty space was already reserved, the addition of a new element becomes very cheap.

No comments:

Post a Comment