Friday, September 14, 2012

Source-Learning-Closure: Day 3

So the start of the 3rd day of digging in Clojure's source, I'm scanning through the code looking for something that I want to dig further into. I'm finding my self relatively happy with the progress I've made so far. Looking over the various functions, I'm noticing that it looks a lot less foreign now.

(cast) calls java's cast, passing in a class and a class name to cast to. (to-array) calls clojure's java wrapper to toArray(), (vector) has a bunch of overloads to constructing a vetor, and so on. Much of those and the one's like (nil?), (false?), (not), and such are really straight forward, many of which just call the java code underneath. All of these become just needing to read up on whats available for our use.

For the most part, I'm scanning the code and all is well. Then I get to the functions for (if-let) and (when-let) and I'm taken back again.

https://github.com/clojure/clojure/blob/1.3.x/src/clj/clojure/core.clj#L1666

I'm reading them a few times, and although I can follow the code, I'm not really sure what they are doing. I also notice that I'm not as comfortable with (let) as I want to be either. So I'm going to look at (let) in more detail first.

I read over the docs and some other info else where like this. I get a better feel for it. I just needed to work through some examples. I won't bore you with to many. The idea with (let) is you create a local scope that you can manipulate your data with.

As I'm digging through let I also run into the (->) function. That one apparently runs through a group of functions in order that you give them. I won't go to much into details about this one here as this post will get really long with every derail, but I figured I'd mention it in passing as I'm about to use it, and I myself didn't know what it was until just now.

user=> (let [message "rawr"] (println message))
rawr
nil

user=> (message)
java.lang.Exception: Unable to resolve symbol: message in this context (NO_SOURCE_FILE:27)
user=> (let [a 1, b 2] (+ a b))
3

user=> 
(let [age (- 2012 1979), decade 10, century 100]
  (-> (- century decade) (+ age))
)
123

That local declaration/logic peers into the future, a decade less than a century from now, to tell me how old I will be. Here's something else I noticed while looking at the examples. I really don't like it for these examples, but I'm sure there's use for it else where.

(let [a 1, b 2] (+ a b)) ; earlier example
3

(let [[a b] [1 2]] (+ a b)) ; can also be written this way
3

These two are the same call, however the second one uses a complex vector. where it maps the first values together and the second values together. In these simple cases I like the first version much beter. When I run into a reason to use the second one, I'll share.

Back on Track

(defmacro if-let
  "bindings => binding-form test

  If test is true, evaluates then with binding-form bound to the value of 
  test, if not, yields else"
  {:added "1.0"}
  ([bindings then]
   `(if-let ~bindings ~then nil))
  ([bindings then else & oldform]
   (assert-args if-let
     (and (vector? bindings) (nil? oldform)) "a vector for its binding"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
   (let [form (bindings 0) tst (bindings 1)]
     `(let [temp# ~tst]
        (if temp#
          (let [~form temp#]
            ~then)
          ~else)))))

The first thing I see that I don't know what it is, is the (assert-args) call. I can make some guesses, but I have a few so I figure its a good one to look up to make sure. After hitting the google, I quickly find out that it's a private function and not an openly accessible one, I won't be calling it with my code. But it's still good to understand for the sake of this exercise. Looking at the source for this one, I see that is runs through the arguments of the function and makes sure that the correct number of arguments were passed in based on what the function want. Seeing that I feel silly as the function was named pretty well and that could have just been implied.

  ([bindings then]
   `(if-let ~bindings ~then nil))

The next part I start looking at is the one above this. Having no idea what's going on here with the ` and the ~ symbols, I do some searching online. It turns out that this is a macro and the ~ symbol is replacing those values with the values being passed in. Lets look at this example:

user=>
(let [msg "rawr"] 
  `(println ~msg)
)
(clojure.core/println "rawr")

With my surface understanding of what's going on here, is we are creating a function that we can run at a later point. Seeing as how macros are a big chapter in a book I'm about to get back to, I'm not going to dig in past the surface for now. However our chunk of code in the if-let that we are looking at, creates a macro to call itself again putting the 'bindings' and the 'then' into place with a nil value. Which turns out is the overload call in this function, so we'll move on to that.

(let [form (bindings 0) tst (bindings 1)]
     `(let [temp# ~tst]
        (if temp#
          (let [~form temp#]
            ~then)
          ~else)))))

We see some heavy use of (let) which is ok, we know it's just setting stuff up locally for the function. then we see this guy: [form (bindings 0) tst (bindings 1)]. I do some searching online and read over the function some more then I realize how silly I was. In this function, bindings is being asserted that it's a vector (array), and you can get the value of a vector at an index by calling the vector as a function. So: ([99, 200] 1) returns 200. I attribute my lack of reading this one to the syntax highlighting. In the source, "bindings" and "assert-args" were the same color, making them stand out. "assert-args" was a private function doing stuff under the hood, I assumed "bindings" was also.

After that last puzzle piece, the whole thing becomes readable to me. Call if-let, pass in some bindings and then a then function and let it rip. As long as all the checks pass, it will bind the values and then call the 'then' function passed in.

Lets see if I'm right and try it.

user=> (if-let [is-young true] (println "young")) 
young
nil

user=> (if-let [is-young false] (println "young"))                
nil

Technically I had the right idea, but I was missing part of the idea of what if-let does. The "if-let" function allows us to streamline a "let" block and a nested "if" statement into one cleaner call. So although my code worked, you really want to pass in the actual "then" statement.

user=> (if-let [is-young false] "young" "old")
"old"

user=> (if-let [is-young true] "young" "old") 
"young"

user=> (if-let [is-young true] (println "young") (println "old"))
young
nil

user=> (if-let [is-young false] (println "young") (println "old"))
old
nil

(if-let [is-young true is-old false] (println "young") (println "old"))
java.lang.IllegalArgumentException: if-let requires exactly 2 forms in binding vector (NO_SOURCE_FILE:0)

user=> (if-let [is-young] (println "young") (println "old"))                  
java.lang.IllegalArgumentException: if-let requires exactly 2 forms in binding vector (NO_SOURCE_FILE:0)

The last few calls, I just wanted to see the exceptions we were looking at if we didn't pass in the correct number of arguments for the vector to be formed from. The author of the code went through the trouble to put them in, may as well get some use out of them. :)

Summary

We looked at the (if-let) code, gaining a better understanding of (let) in the process (which makes sense as I was missing the whole point of (if-let) in the first place), touched the surface of macros, gained a passing understanding of the (->) function, saw a way of writing more complex vectors (which I'm not a fan of yet), and found that there is such a thing as private functions in Clojure.

Better understanding Clojure recur

Just playing around with recur more. I really didn't like the example they had online. After digging around some, I got the idea behind recur, it recalls the function it directly sits under. Mostly I see it used in loops in the examples online, however the = function we were looking at yesterday didn't. It was just calling the normal function it was in.

Quick warning, the first couple of examples won't have a way to stop them. I'm running them in repl, and CTRL+C to stop them. I felt that this best explains what recur is doing.

(defn recur-test 
  [message] 
  (println message) 
  (recur message)
)

user=>(recur-test "rawr")
rawr
rawr
rawr
...
(defn recur-test 
  [message count]
  (println (format "%s: %s" message count))
  (recur message (inc count))
)

user=>(recur-test "rawr" 0)
rawr: 0
rawr: 1
rawr: 2
rawr: 3
...

Let's add the if check into the function so we don't have to keep CTRL+C our repl.

(defn recur-test 
  [message count]
  (println (format "%s: %s" message count))
  (if (< count 5)
    (recur message (inc count))
  )
)

user=> (recur-test "rawr" 0)
rawr: 0
rawr: 1
rawr: 2
rawr: 3
rawr: 4
rawr: 5
nil

It becomes pretty apparent what recur is and is doing at this point. Now when we look at the examples on recur in the docs, it makes more sense for those who haven't seen it before: recur docs