Tuesday, March 27, 2012

Lists, and functions that don't want them

Learning to operate on sequences of data is a continual challenge for me, and probably for other developers coming from an imperative background. I learned a little bit about working with lists yesterday, and I thought I would share it.

Trying to understand "partial" I asked for clarification on IRC. Specifically, I asked whether partial took the next value passed and added it as the rightmost element to the function, as it appeared in the following:

(map (partial + 2) '(1 2 3))
;; => (3 4 5)

When I was told that in fact, partial would take whatever parameters were supplied and add them to the right of the function that was partially declared, I tried removing the map:

((partial + 2) '(1 2 3))
;; => ClassCastException clojure.lang.PersistantList cannot be cast to java.lang.Number

I knew what to change to get it to work:

(apply (partial + 2) '(1 2 3))
;; => 8

I can't say that I understood right away why I needed 'apply' in this example. After some reflection, it seems pretty clear though. I was trying to invoke a function (+ 2 ….) on something. Using '+' with a collection does not work in Clojure.

Adding to a collection is done with 'cons' or 'conj'. I didn't want to put the number 2 into the collection, I wanted to operate on the items in the collection. That is where things like map and apply come in. They reach into the collection and allow you to operate on the members.

map works on each item independently. apply takes all of the items together.

(map (partial + 2) '(1 2 3))
;; => (3 4 5)

(apply (partial + 2) '(1 2 3))
;; => 8

Another function that operates on the members of a list individually is the filter function.

(filter (partial > 10) (range 20))
;; (0 1 2 3 4 5 6 7 8 9)

In the earlier examples the order didn't matter. '+' works the same left to right or right to left. When we use '>' it is important to remember that the values passed in to the function are passed to the right. The results of the filter look backwards if you don't think about how each partial function is applied:

(> 10 0) (> 10 1) .... (> 10 18) (> 10 19)

Do not think of 'partial' as being a place holder, but rather an indicator that more is to come at the end.

2 comments:

  1. It was helpful to talk about partial. Thanks. I'm learning Clojure by bits and pieces.

    ReplyDelete
  2. Why not do:

    (filter #(> 10 %) (range 20))
    ;; (0 1 2 3 4 5 6 7 8 9)

    ReplyDelete