Wednesday, September 12, 2012

Source-Learning-Closure: Day 1 (Update)

As I stated in the post about learning the insides of +

Previous post on Clojure +

That there were some odd calls that I didn't understand. +' instead of just + for example. To shed alittle bit of light on it, not all though, I grabbed the code from the wrong function. In the source it's declared twice. Look at the following:

(defn +'
  "Returns the sum of nums. (+) returns 0. Supports arbitrary precision.
  See also: +"
  {:inline (nary-inline 'addP)
   :inline-arities >1?
   :added "1.0"}
  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (addP x y)))
  ([x y & more]
   (reduce1 +' (+' x y) more)))

(defn +
  "Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'"
  {:inline (nary-inline 'add 'unchecked_add)
   :inline-arities >1?
   :added "1.2"}
  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (add x y)))
  ([x y & more]
     (reduce1 + (+ x y) more)))

So in my examples and exploring, I was digging around for addP and +' and in that case should have been. The actual function + calls "add" and so on. I still don't know what +' is doing yet, but I bet it has something to do with "Supports arbitrary precision." That's another good google search and/or ask a Clojure guru.

The syntaxhighlighter is going crazy! I'm definitely going to need to update that.

Source-Learning-Clojure: Day 1

For the first post in my learning Clojure by reading it, I'm going to try to keep it as simple as possible. Like I stated before, I'm going to assume you can read the most basic syntax of the language. I also dug up the syntax to start using the syntax highlighter I used to use. Hopefully my code examples won't be as painful now, I'll update the highlighter to use actual clojure highlighting soon.

Clojure: +

Returns the sum of nums. (+) returns 0. Does not auto-promote longs, will throw on overflow. See also: +'

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

(defn +
  "Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'"
  {:inline (nary-inline 'add 'unchecked_add)
   :inline-arities >1?
   :added "1.2"}
  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (add x y)))
  ([x y & more]
     (reduce1 + (+ x y) more)))

To get the super basics out of the way, we'll look at the description and meta-data real quick and only this one time. Well, unless something weird comes up in one of them along the way.

Description

  "Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'"

This information comes up in the documentation and such.

Metadata

  {:inline (nary-inline 'add 'unchecked_add)
   :inline-arities >1?
   :added "1.2"}

This is more information that gets pulled up when using the docs in lein repl and such.

Digging In

Ok, with the noise out of the way one of the first things that jump out at me is that the + function is doing more than one thing. It looks like there's overloading available to us.

  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (add x y)))
  ([x y & more]
     (reduce1 + (+ x y) more)))

So I loaded up leiningen (lein repl) and tried out some super simple overloading.

  (defn rawr 
    ([] (println "rawr"))
    ([message] (println (format "%s. rawr!" message)))
  )

And what do you know what...

user=> (rawr)
rawr
nil

user=> (rawr "Whoa")
Whoa. rawr!
nil

...there's Overloading!

This example also tells me that I should remember that these functions always return something, so I should stop using println for testing purposes and just return what I want displayed.

Moving Along

  ([] 0)

Here we see the first overload of the function. It's parameters is an empty vector (or array coming from other languages) []. I found out earlier that is how you create a function that doesn't require any parameters. So if you call it like: (+), it will return the value of that first overload. Looking at the value returned for this, we can safely say that the default value returned from + is 0. So, I'm going to assume at this moment that 0 is the default int value.

Moving Along

  ([x] (cast Number x))

Here we are looking at the overload for passing in a single value. It's taking in that value and casting it to a number. So what happens if we don't pass in a number?

user=> (+ a)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:1)

There was a good expectation, it throws an exception. So, what is Number? I don't recall seeing caps-first-letter in any of the clojure examples so far. So my guess is that it's hitting java with that?

user=> Number
java.lang.Number
user=> FakeyFake
java.lang.Exception: Unable to resolve symbol: FakeyFake in this context (NO_SOURCE_FILE:0)

Yup, it's a java class.

Moving Along

  ([x y] (. clojure.lang.Numbers (addP x y)))

Ok, so there's a couple of things in here I don't know. What's addP? clojure.lang.Numbers? (. ? So lots of questions for this one.

user=> addP
java.lang.Exception: Unable to resolve symbol: addP in this context (NO_SOURCE_FILE:0)

user=> (addP 1 2)
java.lang.Exception: Unable to resolve symbol: addP in this context (NO_SOURCE_FILE:22)

user=> (addP 1 2)
java.lang.Exception: Unable to resolve symbol: addP in this context (NO_SOURCE_FILE:28)

user=> (.addP 1 2)        
java.lang.IllegalArgumentException: No matching method found: addP for class java.lang.Integer (NO_SOURCE_FILE:0)

Yup, definitely don't know what's going on here. And I definitely don't know how to call java code yet, another thing to look up when I get a moment. So I do some google searching for clojure addP and find this info about addP in clojure. So, seeing that we have access to addP in clojure.lang.Numbers.

user=> (clojure.lang.Numbers/addP 1 2)
java.lang.IllegalArgumentException: No matching method: addP (NO_SOURCE_FILE:52)

Tried some other ways, and those didn't pan out. That syntax looked like what I was seeing elsewhere on the web so that's the one I included.

If I remember correctly, the (. is a call to java. So I try this stuff:

user=> (. clojure.lang.Numbers)
java.lang.IllegalArgumentException: Malformed member expression, expecting (. target member ...) (NO_SOURCE_FILE:8)

user=> (. clojure.lang.Numbers 1)
java.lang.IllegalArgumentException: Malformed member expression (NO_SOURCE_FILE:9)

user=> (. java.lang.Numbers)     
java.lang.IllegalArgumentException: Malformed member expression, expecting (. target member ...) (NO_SOURCE_FILE:10)

user=> (. java.lang.Numbers 1)
java.lang.ClassNotFoundException: java.lang.Numbers (NO_SOURCE_FILE:11)

user=> (. java.lang.Numbers (+ 1 1))
java.lang.ClassNotFoundException: java.lang.Numbers (NO_SOURCE_FILE:12)

user=> (. java.util.Date)
java.lang.IllegalArgumentException: Malformed member expression, expecting (. target member ...) (NO_SOURCE_FILE:18)

So, I start asking around and get these:

user=> (java.util.Date.)                            
#

user=> (. (java.util.Date.) getTime)                
1347486819531

The "." is calling a method on a java object when it's "(." and it's creating a new java object when it's after the java object "Date.", That makes sense enough to move forward.

  ([x y] (. clojure.lang.Numbers (addP x y)))

If I'm saying this right: Call the method addP on the underlaying clojure/java Numbers, code passing in X & Y, and return that result. If that's correct, I'm going to assume based on it's naming, and the naming/code of the underlaying java code that addP adds two values together.

Moving Along

And finally the last overload of the + function:

  ([x y & more]
    (reduce1 +' (+' x y) more)))

One of the big things that jump out here is that it's calling itself as part of this function overload to perform its tasks. It's calling (+ x y) and it's passing itself "+" into it's reduce function. This seems like pretty standard stuff, but figured it would be worth noting that it's a functional language and that my guess would be you can't call an overload in one that precedes it.

user=> (defn fn-order
  ([] "first")
  ([message] (format "%s and then second" (fn-order)))
)
#'user/fn-order

user=> (fn-order)
"first"

user=> (fn-order "woot")
"first and then second"
user=> (defn fn-order                                      
  ([message] (format "%s and then second" (fn-order)))
  ([] "first")                                        
)           
#'user/fn-order

user=> (fn-order)                                          
"first"

user=> (fn-order "woot")                                   
"first and then second"

The first one fired off as I would have expected it to, however I was surprised to find that declaring them in the other order, everything worked! That's definitely something I'll need to ask one of our Clojure/Functional gurus.

After that, the "& more" jumped out. It's a way to call arguments/parameters optionally. The "&" can take any number of params and assigns them to, in this case "more". Later when you call the optional arg, it has all of the values after the required ones assigned to it. So if you were to call this function with say: 1 2 3 4, then the "more" would be (3 4).

user=> (defn test-optional-args                              
  [x y & more]                                          
  more
)
#'user/test-optional-args

user=> (test-optional-args 1 2 3 4 5 6 7 8 9 10)             
(3 4 5 6 7 8 9 10)

One part of the source that's kind of throwing me off is things like "reduce1". Calling that from repl results in errors, but its in the source. Hopefully that'll make sense the further I get into this. Aside from that, using "reduce" in repl instead sheds some light into what it's doing. for reduce you use as such: (reduce f coll). So, you call reduce, passing in the function you want to apply to the values in the collection and then the collection. It will then go from the first value and apply the function to the second value. Then continue that process over and over until it runs through the collection. Lets look at it in actual code.

user=> (reduce + [1 2])
3

user=> (reduce - [1 2])
-1

user=> (reduce * [1 2])
2

"reduce" also has an overload: (reduce f val coll). Where the f is the funciton and coll is collection, val is a value to add to apply before beginning the walk through of the collection. In our "+" functions case: (+ x y). So, for this overload we are adding X & Y, and then adding all of the other values passed into the optional args.

Another thing that doesn't work for me is the "+'" calls in the source. I'll show you an example. I'm beginning to think I've missed something somewhere. The only difference is not including the ', then in repl all is good.

user=(defn test+ ([x y & more] (reduce +' (+' x y) more)))  
#'user/test+

user=> (test+ 1 2 3 4)                                      
java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to java.lang.Number (NO_SOURCE_FILE:0)

user=> (defn test+ ([x y & more] (reduce + (+ x y) more)))  
#'user/test+

user=> (test+ 1 2 3 4)                                    
10

Experiment

When I wrote the "test+" function to work through reduce, I realized that, even though I was reading the source code that showed the example, that + was declared in the code as the function name. Looks like there aren't any and/or few restricted values that names can be. So I wanted to test this out a little:

user=> (defn TK-421 [] "Why aren't you at your post?")
#'user/TK-421

user=> (TK-421)                                       
"Why aren't you at your post?"

Ha, now that's a function! But what happens if I override a function that already exists? Say the + function:

user=> (defn + [] "rawr")
WARNING: + already refers to: #'clojure.core/+ in namespace: user, being replaced by: #'user/+
#'user/+

user=> (+)
"rawr"

user=> (+ 1)
java.lang.IllegalArgumentException: Wrong number of args (1) passed to: user$-PLUS- (NO_SOURCE_FILE:0)

user=> (+ 1 2)
java.lang.IllegalArgumentException: Wrong number of args (2) passed to: user$-PLUS- (NO_SOURCE_FILE:0)

So, we overrode the + function, which overwrote all of its overloads. Or better said: Our awesome override, overrode, the old overloads...

Summary

Day 1 in the source and I've learned that we have; Overloading in Clojure, Overloading can call its other overload methods even if they were declared afterwards, Reinforced that functions always return, reinforced that we can create parameterless & optional parameter functions, that "." is (so far) used to call the underlaying java code, Reduce takes a function and a collection and then applies the function to all of the values in the collection, that the source is written slightly differently than the repl expects Clojure to be written, that you can override the already existing functions, and that I'm going to assume for now that naming has little to no restrictions.

Learning Clojure: needing a different approach.

I'm having issues with learning Clojure. I'm picking up bits and parts, but the more I read online and in the books the more I find stuff that isn't sinking in. On top of that the tabs in my browser exploded with blog posts, articles, examples, and so on. It's time to try learning clojure a different way; by digging through the source.

I'm going to start off as small as possible, and see where it leads. I'll post as much as I can about it in the hopes of sounding like I don't know what I'm talking about and someone out there correcting me. I'll need to make the assumption that one can read the basic Clojure syntax, but not much beyond that.

I'm going to use these as my resources, unless I find or am given something better.

http://clojure.org/cheatsheet

http://clojuredocs.org/clojure_core

https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj

If you're interested in the future posts about this, I'll tag them all with source-learning-clojure.