Thursday, September 13, 2012

Source-Learning-Closure: Day 2

Previously we looked at the clojure source for the + function and after looking at. Today, we're going to look through the code for the = function.

Clojure: =

Equality. Returns true if x equals y, false if not. Same as Java x.equals(y) except it also works for nil, and compares numbers and collections in a type independent manner. Clojure's immutable data structures define equals() (and thus =) as a value, not an identity, comparison.

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

(defn =
  "Equality. Returns true if x equals y, false if not. Same as
  Java x.equals(y) except it also works for nil, and compares
  numbers and collections in a type-independent manner.  Clojure's immutable data
  structures define equals() (and thus =) as a value, not an identity,
  comparison."
  {:inline (fn [x y] `(. clojure.lang.Util equiv ~x ~y))
   :inline-arities #{2}
   :added "1.0"}
  ([x] true)
  ([x y] (clojure.lang.Util/equiv x y))
  ([x y & more]
   (if (= x y)
     (if (next more)
       (recur y (first more) (next more))
       (= y (first more)))
     false)))

This one starts off pretty simple and much like the last one, so we do some testing with the first couple overloads to see what comes back:

user=> (= 1)
true

user=> (= "")
true

user=> (= nil)
true

The first overload is what you would expect, passing anything into it returns true. I might be looking at this wrong, but this seems kind of pointless.

user=> (= 1 1)
true

user=> (= 1 2)
false

user=> (= 1 "1")
false

user=> (= 1 :1)
false

user=> (= nil nil)
true

No surprises there. And then for the last overload:

([x y & more]
   (if (= x y)
     (if (next more)
       (recur y (first more) (next more))
       (= y (first more)))
     false)))

Off the bat we've got if statements, next/first calls and recur. The if statements are pretty straight forward in Clojure: (if test then else) if check succeeds return value else return other value.

user=> (if true "yes" "no")
"yes"

user=> (if false "yes" (if true "nested yes" "nested no"))
"nested yes"

So we have nested if statements, where either value returned can be a function call, after all functions always return. Next and First are functions to navigate through collections, next gets the next item in the collection, or "more" in this case, where first grabs the first item. Which leaves us with recur. This isn't one I'm familiar with so lets go look it up and see what can be done with it.

The more I searched through the docs, and online resources the more I realized I was assuming recur to be something other than what it is. After digging around a bit, I realized that it wasn't something to be used on it's own. It's purpose is to re-call the function that it's contained in passing in a function to apply before it's next call. The examples online mostly point to loops and such. I then realized I had no idea how to do a loop in Clojure, so I started playing around with that. After much pain I figured out what this example was doing and had a better understanding of loops and recur.

user=> (loop [x 10]
  (when (> x 1)
    (println x)
    (recur (- x 2))))
10
8
6
4
2
nil

So I wanted to put myself to the test. I wanted to create something that wasn't in clojure using a loop & recur. The first thing that jumped into my head was programming's ++ incremental call. But seeing as how that is totally yesterdays math, I want to implement a +++ function! That's right increment by 2, not 1.

As I was digging into it, I realized I had bigger issues...

user=> (defn +++ [val] 
  (loop [i 0] 
    (if (< i 2) (do 
      (+ val 1) 
      (println val)
      (recur (+ i 1))
    ))
  ))
nil
#'user/+++

user=> (+++ 1)
1
1
nil

How was I going to update the parameter passed in, incrementing it and keeping the new value? I tried setting a local variable and updating that, a global variable would probably work but I didn't want to go there, and some random exception throwing syntax goof-rounds. It hit me pretty hard, I've got a lot to learn about Clojure & functional programming in general. So I start looking for other examples online, which eventually led me to a loop that had two sets of values in the loop call: (loop [i 0 j 99]... for example. And in those cases the recur call passed in two functions to apply to the two sets in the loop. That gave me enough to put together a loop that incremented on "i" and updated a value on the second set. Here's what I came up with:

(defn +++ 
  [val] 
  (loop [i 0, return-val val] 
    (if (< i 2) 
      (do 
        (inc return-val) 
        (recur (inc i) (inc return-val))
      )
 
      return-val
    )
  )
)
#'user/+++

user=> (+++ 1)
3

user=> (+++ 99)
101

After a bunch of trial & error, troubleshooting and repl running, I created a +++ incrementing function in Clojure!

Back on Task

([x y & more]
   (if (= x y)
     (if (next more)
       (recur y (first more) (next more))
       (= y (first more)))

So now what do I think the last overload is doing in the = function of Clojure? The syntax is different than the recur & loop blocks I was playing with, but I think that's saying if the first to parameters are equal, continue checking the "more" to make sure they are equal. By which, if there is a "next" item in "more" recur call the whole function ([x y & more]) passing in y to x, the first in "more" and the next of "more" to re-fire off the check. And when it gets to the end, if there aren't any additional values in "more", check the last y we've got against the last (first in a collection of one) value to see if they are equal.

Summary

So, we've looked at the = function, and in tandem most of the other equality functions = == < &rt; and so on, as they were all pretty similar. But what I got from today's digging is that, understand the basics of loops/do/while, and can now read recur logic. We'll at least the simple recur logic I've shown here, it feels like recur has some hidden powers I don't see yet. Oh, and I extended Clojure with the much needed +++ incremental math! :)

The other thing that I'm starting to see, is the difference in functional programming as compared to object-oriented. Up until now, I had only been seeing a completely different syntax, nothing about different thought processes. Now that I'm getting to understand & read Clojure better, I'm starting to but up against the difference in programming thoughts.