Sunday, December 26, 2010

Thinking about state is hard

I'm finally getting the hang of clojure syntax, but I'm still having trouble living in its immutable world.  I understand the idea that functions take inputs and yield outputs, but I'm still trying to stop thinking that some data structure I need is itself changed by a function.  It's not different really than something like this:

y = 10


def changeVar(x):
    x = 20
    return x


print "Output of function = ", changeVar(y), " Value of y is still ", y


y = 20
print "Now y = ", y

Which of course would print:
Output of function = 20 Value of y is still 10
Now y = 20


However, this is a trivial example.  The reason the function changeVar() doesn't change the value of y is because the local variable x is in effect a different variable (it has a lexical binding local to the function, as opposed to the global variable y).  In clojure, with a few exceptions, EVERYTHING is immutable.  Once some symbol is bound to a variable, that's its value forever more unless you have made it a ref, agent, atom or var (though you can shadow a global symbol with a local one).

To give you a more concrete example of something troubling me, I wanted to create a map of various swing components I have created.  I created a function called make-row-fn which builds three swing components: a JLabel, a JTextField, and a JButton.  Each of these three components are then added to a JPanel object.  Instead of getting components via JPanel's getComponent() method, I would rather have these components readily available in a map...for example:

(make-row  [ jpanel label-string button-string ]
   (let [ lbl (JLabel. label-string)
           fld (JTextField. 15)
           btn (JButton. button-string) ]
      (doto jpanel
        (.add lbl)
        (.add fld)
        (,add btn)
       {:label lbl :field fld :button btn} )))

(def components {} )
(doto components
  (assoc  :ext-jar-row (make-row "External Jars" "Browse"))
  (assoc :classpath-row (make-row "Classpath Dir" "Browse"))
  (assoc :clojars (make-row "Clojure files" "Browse")))


If you don't see why this won't work, consider what is happening to the components map on each assoc call.  The map components starts empty.  So the first call to assoc will yield:

{:ext-jar-row {:label lbl  :field fld  :button btn}}

On the second call to assoc, you might think components would equal the following:
{:ext-jar-row    {:label lbl  :field fld  :button btn}
 :classpath-row {:label lbl  :field fld  :button btn}}

In languages with mutable hashes/maps/dictionaries, this would be true.  But remember, Clojure returns a NEW map with the value of the original input having an added key-value pair you specified.  So instead, on the second call, the returned dictionary would be:

{ :classpath-row{:label lbl  :field fld  :button btn}}


Worse, components itself is still empty.  So how do you make each assoc call (or any function that returns something you want appended to it) make the original?  One answer is to loop through with recursion and rebind components on each loop.  Another (potential) answer is to use (comp) to compose the functions.  For example I could do this manually:

(assoc
  (assoc
    (assoc components :ext-jar-row  (make-row ...)) :classpath-row (make-row ...) ...)

If you look at that, it obviously looks recursive.  But can you see that it's also a composition of functions?  If not, remember what a composition is in mathematical terms.  If I have two functions:

x = g(h)
y= f(x)

Then I could compose the two like y = f(g(h))

Basically the output of one function is the input of another.  What happened above with the nested assoc calls is in essence a composition.  Earlier, I said that (comp) was a potential solution.  Unfortunately, I have not figured out how to use comp on complex functions.  The call to assoc takes 3 args, the map to associate to, a key and a value.  So I tried this...

(def nested-assoc (comp assoc assoc assoc)) ; a three deep composition of assoc

But how do I use nested-assoc?  That's what I haven't figured out how to do yet.  So for the time being, I'll use a loop/recur instead.  I'm not positive I can use comp, since it appears from the doc that although one of the functions can take variable args, it says the result of the rightmost composed function is fed to the next (more left) function.  This seems to imply that any function other than the rightmost can only take one arg.  Hmm, perhaps I can make this work with partial functions...since I know all the values of my args except the map itself.

Unfortunately, I have also realized two other things about clojure.  The first is what I related to above...namely that trying to model a problem without mutating state is still something I'm grappling with.  My code has too many global symbols I think, and perhaps I am still thinking too imperatively.  This might also be because I am writing Swing GUI code which heavily relies on Java, which in turn means I'm bringing a lot of Java baggage with me.  I wish there was more Clojure swing code I could look at.  Stuart Sierra's blog on mixing clojure and swing has been tremendously helpful, but as is usual with most tutorials or blogs, once things get complex, the examples aren't all that fruitful anymore.

The second problem is related to the first.  I think clojure needs more "native" clojure libraries.  I know one of the reasons behind the design of clojure was that it is supposed to be pragmatic.  One of the pragmatic choices of clojure is that it can leverage all the existing java libraries already out there.  The problem with this however is that you also bring all the ills of Java with you....mutating state, the need to use proxy to implement abstract classes or interfaces, etc.  I think some people in the clojure community might not like the idea of reimplementing something that already exists in Java....then again, many things already have been ported to pure clojure (the xml libraries come to mind).

Clojure is still a very young language, and I hope it gets more support to build the infrastructure it needs to be even more prosperous.  I really think that more people would be willing to use it if people didn't think it was so hard.  I think one of the reasons it seems that Scala has more momentum is that it seems more familiar.  It only dips its toe into functional waters.  I think Scala is shooting itself in the foot with its hybrid approach.  On the one hand, the learning curve is easier, because people learning it can only use the functional aspects as they need it.  This will create some Scala code that is heavily functional, and some that is heavily OO, and some that are a hybrid.  If you don't know all paradigms, some Scala code may appear very foreign.

In fact, I see in Scala the beginnings of the things I hate about perl.  Now admittedly, I've hardly written any Scala code (I only got to chapter 10 in the Programming Scala book by Odersky before I abandoned it for Clojure), but I already saw how some things were optional and there were different ways to do the same thing.  This "There Is More Than One Way To Do It" (TIMTOWTDI) is in my humble opinion a horrible approach when trying to learn a language.  The problem is compounded when you have not only different syntax to do the exact same thing, but you have more than one paradigm to program in.  However, I do like Scala's pattern matching and traits, and Scala has shown that you can have immutable objects (where I think they went wrong is that Scala enforces functional paradigm by convention rather than force...immutable state via var's are just as first class as immutable data via val's).

"But you like python!! That's a multi-paradigm language!".  This is true, but python's syntax is very simple compared to Scala (and there's preferably only one way to do it).  Also, python's functional roots are somewhat weak: lambdas have limitations, python has no concept of immutable data (and no, strings don't count...you can't make a class final, or define a variable as a constant).  So although python does have full closures (just not good anonymous functions), and you can declare, return and pass in functions (or classes) as first class citizens, many other things just aren't all that functional.  Of the python code I have seen, very little of it has ever looked functional other than the occasional lambda, passing in of a function as a callback, or having a class implement __callable__.  Scala on the other hand bills itself as a functional language, and many have taken issue with this.

Moreover, Scala will have the same "issue" with Java that clojure does.  Namely that if Scala is written in a functional style, it will have the same interoperability issues.  Of course Scala code IS Java code (just like clojure code IS java code), but that doesn't mean that they will play nicely.  However, because Scala is a hybrid language, I surmise that it can interoperate with Java more seamlessly than Clojure can, and this is probably another reason for its greater uptake (so far).

So I think the harder part to learning clojure isn't the lisp-y syntax, it's the functional paradigm.  Honestly, getting used to s-expression syntax isn't that bad, and clojure is much nicer thanks to the [] and {} notation.  But I'm still struggling with the functional side, though I think that's just a matter of time.  I remember when I first started learning LabView, a dataflow oriented language, I had a really hard time understand the code flow.  Unlike non data-flow languages, data flow languages (like VHDL) execute when inputs are valid.  So data flow languages excel at visualizing and implementing parallelization.  The order in which functions are executed isn't based on a top-to-bottom writing of the code, nor left to right (LabView is a "graphical" language where you connect nodes/functions via wires, and the wires represent inputs and outputs).  Eventually, I kind of got it, but it took several months.

When I hear people say, "once you know one language, it's easy to pick up any others", I know they haven't learned that many languages.  To make an analogy with spoken languages, once you know one romance language, it's not hard to pick up others.  Italian is similiar enough to Spanish which is similar enough to Portuguese which is similar enough French...etc etc.  And English, though a Germanic language, also has a lot of borrowed French words (thanks to the Norman conquest of England), so it's not too hard for English speakers to pick up on romance languages.  But to go from English to Chinese?  Not only is the alphabet totally different, it's a tonal language (which English has no comparison to).

The functional paradigm is quite a bit different than the imperative model.  When you go from  C++ to Java to python to perl to ruby, you're pretty much staying in the imperative and object oriented world.  Although clojure is my first and only language, I can see that it picked up a lot from Haskell (laziness, STM, functional paradigm) which kind of makes clojure a love child of lisp and haskell (or maybe scheme and haskell?).  But I can say that learning lisp has been an interesting challenge.

No comments:

Post a Comment