clj.orcery

Language, Expression and Design

Saturday

26

October 2013

If you can't beat them..

by Chris Zheng, on clojurescript, purnam, javascript, macros, angularjs

Apologies for spamming Planet Clojure with the User's Guide to ghost (which by the way is an amazing blogging platform). This is my first 'official' clojure post, sharing my experience working with clojurescript and it is split into three related but distinct parts:

  • Embracing Javascript
  • Introducing Purnam.
  • Clojurescript Macros.

Embracing Javascript.

Having come from an image processing background, I never took javascript seriously. It was something of a necessary evil. Something that had to do be done order to make the client happy. I was never happy writing javascript. Client-side programming felt a little like going to the toilet and then forgetting to wipe.

It all changed with clojurescript. Excited at the prospect of readable, modular, reusable and managable client-side code, I needed no encouragement to jump right in. However, after a frustrating week of writing a todo-list, I had to re-evaluate my strategy.

I realised a deeper knowledge of JS was needed before anything substantial could be done in clojurescript. I started taking an interest in the JS landscape and it then occured to me that the clear advantages clojure had on the JVM just did not translate over to the browser:

  1. functional programming. Javascript is a functional language and the best JS libraries are built using this paradigm.
  2. generic datastructures. JSON has become the franca-lingua of the web. It is the generic datastructure for data in the browser and it won't be going away anytime soon. Any half decent clojurescript application requires wrangling with JSON and I found that I was using cljs->js way too often.
  3. interactive development. Javascript has by far the best tools for interactive development on the the browser. In this case, clojurescript actually is at a significant disadvantage because it has to be compiled. In my clojurescript projects, I actually found myself typing much more javascript because I was doing most of my debugging within the browser console.
  4. better libaries. All three of the above factors as well as the huge pool of developers using javascript means that there is an amazing wealth of libraries. For every one clojurescript library, there would be three with similar functionality written in coffeescript and five more in javascript. JS frameworks such as angular.js are now much more popular than the the clojure the language itself.

The more I looked into javascript, the more compelling it became to me. Reflecting back on this after 2 years, I would say that clojure only has one clear advantage over JS, that is, its macro system and the ability to extend language level features. A great example of this is core.async and amazing abstraction that it brings. When I have time, I'll start looking at porting ribol over to clojurescript so that conditional restarts can be used on the browser as well. However, other than macros, anything in clojurescript can be written in javascript (albeit uglier).

What tipped me over the edge into clear JS territory was angular.js. angular.js allowed the programmer to write directives - effectively giving the power of macros to html elements. Concerns about modularity and testability was also addressed, although the framework forced the programmer to write some boilerplate setup code for every module.

However, about a week into angular.js, it was clear to me that javascript had some serious firepower. There was no way that I could justify using any clojurescript frontend framework when angular.js could do everything and more. On the functional side, Javascript already had map and reduce built-in. Furthermore, libraries like sugar.js contained all the functionality for collections and sequences. The only reason that kept me in clojurescript land was the joy of clojure syntax and the dread of writing something like this:

function (a) {  
   return function (b) { 
      return function (c) {
      };
   };
 };

So I made clojurescript embrace javascript.

Introducing Purnam

I wrote purnam because I wanted a way to 'talk' javascript when programming clojurescript. By 'talk', I wanted to have a way to mentally think in javascript such that there is a direct translation of the language. This is an example of a login implementation in javascript:

angular.module("my.app").controller("MainCtrl",  
  function($scope, $http){
    $scope.msg = "";
    $scope.setMessage = function(msg){
      $scope.msg = msg
    };
    $scope.loginQuery = function(user, pass){
      var q = {user: user, pass: pass};
      $http.post("/login", q)
         .success(function(res){
           if (= res "true"){
             $scope.loginSuccess = true
           } else {
             $scope.loginSuccess = false
           }
         })
         .error(function(res){
           console.log("error!!")
       });
      }
  });

This is the same example using clojurescript and purnam:

(def.controller my.app.MainCtrl [$scope $http]
  (! $scope.msg "")
  (! $scope.setMessage (fn [msg] (! $scope.msg msg)))
  (! $scope.loginQuery
     (fn [user pass]
       (let [q (obj :user user
                    :pass pass)]
         (-> $http
             (.post "/login" q)
             (.success (fn [res]
                         (if (= res "true")
                           (! $scope.loginSuccess true)
                           (! $scope.loginSuccess false))))
             (.error (fn [] (js/console.log "error!!")))))))

Notice is that there is alot less noise. Also, the boilerplate code for initialisation has been abstracted away using def.controller. However, the key is that there is a one to one correspondence between the javascript code and the clojurescript code. I can eyeball the javascript code to write clojurescript code and vice-versa without doing any translating of concepts. It is very handy for working with JS examples.

A larger example can be seen in here where I followed almost line by line, a tutorial for the crafty.js game engine. Yes, I know the code is extremely non-functional. Yes, I know that using the this keyword makes for potentially bad coding. The important point is that it WORKS. A working example sure as hell beats me trying to write my own game engine in clojurescript because I wanted my code to be more 'clojurish'.

Clojurescript Macros

Whilst building purnam, I had to write alot of macros and ran into the problem of how best to test them. I started a question on stackoverflow but was a little bit surprised at the lack of response. So I'm going to attempt to document a little bit about my own experience testing macros.

I don't set out to write macros. Macros happen because I see myself writing a particular piece of code and I think to myself - geez that is one ugly block of code, I wonder if I can abstract that out somehow?. Usually functions suffice, but time and time again I turn to macros.

Macros are great because it turns the programmer into a language designer. For example, The javascript code

account.user.name = "Chris" 

can be written as

(aset-in account ["user" "name"] "Chris")

where aset-in is like assoc-in but works with js objects.

However, I wish to have assignment be written like this:

(! account.user.name "Chris")

Because I require the javascript dot.syntax notation, I need a macro that converts:

(! account.user.name "Chris")
=> (aset-in account ["user" "name"] "Chris")

Here are how my test definitions for the ! macro:

(macroexpand-1 '(j/! <OBJ>.<V1> <VALUE>))
=> '(purnam.cljs/aset-in <OBJ> ["<V1>"] <VALUE>))

(macroexpand-1 '(j/! <OBJ>.<V1>.<V2> <VALUE>))
=> '(purnam.cljs/aset-in <OBJ> ["<V1>" "<V2>"] <VALUE>)

(macroexpand-1 '(j/! <OBJ>.|<V1>|.<V2> <VALUE>))
=>  '(purnam.cljs/aset-in <OBJ> [<V1> "<V2>"] <VALUE>)

I use angle brackets to make it clear to me what code is undergoing transformation. It is quite a nice visual representation, especially when I am trying to see what a macro is doing. Notice that the difference between the second and third example is quite subtle where the piped notation |<V1>| does not turn the variable into a string. Other elementry macros are tested the same way.

This convention works even better on more complex code. I am testing the describe.controller macro below:

(macroexpand-1
 '(describe.controller
   {:doc "<DESC>"
    :module <MODULE-NAME>
    :controller <CONTROLLER-NAME>}
   <BODY>))
=>
'(let [spec (js-obj)]
   (describe {:doc "<DESC>"
              :module <MODULE-NAME>
              :controller <CONTROLLER-NAME>}
             (js/beforeEach (js/module "<MODULE-NAME>"))
             (js/beforeEach
              (js/inject
               (array "$rootScope" "$controller"
                      (fn [$rootScope $controller]
                        (! spec.$scope ($rootScope.$new))
                        ($controller "<CONTROLLER-NAME>" spec)))))
             <BODY>)))

It can be seen that a lot of code is being generated with this macro. However, with the <CAPS> form, I can clearly identify what the macro is doing.

Conclusion

I believe that until clojurescript (or any other language) gets first class support in the browser, javascript will be the overwhelming king of the web. Until that day, I believe that we as clojurescript developers should play to the strength of the chemeleon-like powers of lisp and write more macros.

An important part of this process is to be able to test and verify macros. I hope that this post helps others find their way and bring about more techniques for dealing with them.

comments powered by Disqus