ANN: clojurejs -- a Clojure (subset) to Javascript translator

91 views
Skip to first unread message

Ram Krishnan

unread,
Jan 11, 2011, 12:09:58 AM1/11/11
to Clojure
Hi all,

I've just released a stable version of `clojurejs' -- an
unimaginatively named Clojure library for translating a Clojure subset
language to Javascript.

clojurejs is something I've been working on for a few weeks as part of
a larger web app in Clojure. The code's a bit crufty (reflects my
incremental discovery of the inconsistencies in Javascript), but
functional and I wanted put something out there for people to check
out. I welcome bug reports and feedback. It's been useful for my
specific needs, and I'd be happy if it's even marginally useful to
others.

I realize there are a number of other efforts to compile/translate
Clojure (or other Lisp subset) to Javascript, but nothing quite fit my
requirements, which prompted me to build clojurejs. Some useful
aspects of clojurejs are:

* Consistent scoping in let and loop/recur forms
* Macros
* Implicit return
* loop/recur translates to Javascript for loops
* Translates Clojure vectors, strings, keywords, symbols and maps to
Javascript equivalents
* dot form access to methods and properties
* Adds a couple of Clojure incompatible special forms for Javascript
specific behavior

If this seems interesting I've posted a more detailed entry on my blog
(http://cynojure.posterous.com/clojurejs-a-clojure-subset-to-
javascript-tran).

Oh, and the github link: http://github.com/kriyative/clojurejs

Look forward to feedback.

Cheers,

-ram

Daniel Werner

unread,
Jan 11, 2011, 6:55:45 AM1/11/11
to Clojure
Hi Ram,

your take on Clojure to JS translation seems very interesting to say
the least. Thanks for sharing your work.

A few points I tripped over while reading the example:
* Why are functions being defined as "join = function (...)" instead
of "function join (...)"? Does this make a semantic difference?
* "if { ... } else { ...} break;" is probably missing a semicolon
before "break". Don't trust JS' implicit semicolon-adding ;-)
* The whole function body is being wrapped in a "return function()
{ ...}()". Is this done in order to support implicit return? My guess
is that deeply nesting such constructs could become quite performance-
heavy due to the many additional function calls.
* Mozilla's JS 1.7 supports a let statement[1] with lexical scoping,
though implementation support outside of Firefox is scarce. Depending
on your target audience and performance requirements it could be
worthwhile to implement this as an optimization instead of using the
"function(){...}()" trick, but only if allowed by some flag,
like :features #{:let} or similar.

I'm going to take a deeper look into this once I have the time.

[1] https://developer.mozilla.org/en/New_in_JavaScript_1.7#Block_scope_with_let

Daniel

Ram Krishnan

unread,
Jan 11, 2011, 10:20:48 AM1/11/11
to Clojure
On Jan 11, 3:55 am, Daniel Werner <daniel.d.wer...@googlemail.com>
wrote:
> Hi Ram,
>
> your take on Clojure to JS translation seems very interesting to say
> the least. Thanks for sharing your work.

You're very welcome, and thanks for the quick feedback.

> A few points I tripped over while reading the example:
> * Why are functions being defined as "join = function (...)" instead
> of "function join (...)"? Does this make a semantic difference?

This stems from my misunderstanding of Clojure's `defn' form. I had
started implementing Scheme like scoping for functions where,

(defn test1 []
(defn test2 [] (+ 1 1))
(= (test2) 2))

would generate:

var test1 = function () {
var test2 = function () { return 1 + 1; };
return test2() == 2;
};

This effectively would hide `test2' from the toplevel, but this isn't
the behavior of `defn' as I discovered. So, by dropping the leading
`var' in the generated Javascript, I could make all the inner
functions exposed at the top level as well. I believe there's no
scoping semantics difference between the `x = function () {...}' and
`function x () {...}' forms, unless `x' is declared somewhere in the
scope as a local var. Of course, the scoping semantics completely
change with the explicit introduction of `var x = function () {...}'.

> * "if { ... } else { ...} break;" is probably missing a semicolon
> before "break". Don't trust JS' implicit semicolon-adding ;-)

You're absolutely right. I'll add a test case and fix that today.

> * The whole function body is being wrapped in a "return function()
> { ...}()". Is this done in order to support implicit return? My guess
> is that deeply nesting such constructs could become quite performance-
> heavy due to the many additional function calls.

You're probably right. As I worked on the let/loop forms I suspected
they were probably going to have performance issues in the long run,
but I really really wanted to have consistent scoping rules. I traded
off the performance issue in the expectation that with the race to
build the fastest Javascript JIT compiler, Google, Mozilla, or Apple
will eventually solve these crazy performance gotchas in Javascript.

> * Mozilla's JS 1.7 supports a let statement[1] with lexical scoping,
> though implementation support outside of Firefox is scarce. Depending
> on your target audience and performance requirements it could be
> worthwhile to implement this as an optimization instead of using the
> "function(){...}()" trick, but only if allowed by some flag,
> like :features #{:let} or similar.

That's an interesting idea, although I'm not too keen on specializing
for any one browser. The other problem is I don't see any reasonable
way of providing the alternates from a web server without some form of
user-agent sniffing.

>
> I'm going to take a deeper look into this once I have the time.
>
> [1]https://developer.mozilla.org/en/New_in_JavaScript_1.7#Block_scope_wi...
>
> Daniel

Daniel Werner

unread,
Jan 12, 2011, 8:56:42 AM1/12/11
to Clojure
On Jan 11, 4:20 pm, Ram Krishnan <kriyat...@gmail.com> wrote:
> > * Mozilla's JS 1.7 supports a let statement[1] with lexical scoping,
> > ...
> That's an interesting idea, although I'm not too keen on specializing
> for any one browser. The other problem is I don't see any reasonable
> way of providing the alternates from a web server without some form of
> user-agent sniffing.

You're right, any potential benefits could only be reaped under very
specific circumstances. Making the codebase more complex to optimize
for these limited cases doesn't make sense.

I've taken a deeper look yesterday and am positively surprised how
much easier to understand the implementation is than expected. Very
cool. A few design decisions caught my eye though, and could IMO be
improved:

Using a ref won't actually improve your concurrency experience with
*macros* (provided I understand your code correctly). Since the
parsing and emitting is written in an inherently linear style, there
will only ever be one transaction altering *macros*. More problematic
though is the case where two threads happen to concurrently translate
completely independent (js ...) expressions. Since *macros* is global,
both threads would share the same macro definitions even if totally
different code bases are being translated! This problem could be
mitigated by using thread local bindings for *macros*. Refs or atoms
are not neccessary in this case; even plain old (set!) would do.

Another thing that strikes me as a potentially bad idea is the
reliance on imperative behaviour to generate output, i.e. "emitting"
or "printing" generated code to a stream instead of returning the
pieces in a functional way and combining them afterwards. This
imperative style could hamper your code's composability in the
future. ... What do others think?

With all that said -- it's actually quite pleasant to work with right
now. If you're interested in patches, I've done some small
refactorings to remove duplicate code, added basic docstring support
and other small fixes. Please see my fork at:

https://github.com/danwerner/clojurejs

Daniel

Ram Krishnan

unread,
Jan 12, 2011, 12:24:59 PM1/12/11
to Clojure

On Jan 12, 5:56 am, Daniel Werner <daniel.d.wer...@googlemail.com>
wrote:
> On Jan 11, 4:20 pm, Ram  Krishnan <kriyat...@gmail.com> wrote:
>
> > > * Mozilla's JS 1.7 supports a let statement[1] with lexical scoping,
> > > ...
> > That's an interesting idea, although I'm not too keen on specializing
> > for any one browser. The other problem is I don't see any reasonable
> > way of providing the alternates from a web server without some form of
> > user-agent sniffing.
>
> You're right, any potential benefits could only be reaped under very
> specific circumstances. Making the codebase more complex to optimize
> for these limited cases doesn't make sense.
>
> I've taken a deeper look yesterday and am positively surprised how
> much easier to understand the implementation is than expected. Very
> cool. A few design decisions caught my eye though, and could IMO be
> improved:

Glad you found the code readable, which is always a concern with Lisp
code that has evolved over the course of its author discovering the
problem he's setting out to solve :)

>
> Using a ref won't actually improve your concurrency experience with
> *macros* (provided I understand your code correctly). Since the
> parsing and emitting is written in an inherently linear style, there
> will only ever be one transaction altering *macros*. More problematic
> though is the case where two threads happen to concurrently translate
> completely independent (js ...) expressions. Since *macros* is global,
> both threads would share the same macro definitions even if totally
> different code bases are being translated! This problem could be
> mitigated by using thread local bindings for *macros*. Refs or atoms
> are not neccessary in this case; even plain old (set!) would do.

I agree. Funnily enough, I started out with a thread local binding
implementation, and switched to a ref because I needed macro
definitions to persist across multiple requests. Consider something
like the following:

(defroutes my-routes
(GET "/js/app.js" {:content-type "text/javascript"
:body (tojs
(resources-path "boot.cljs")
(resources-path "app.cljs"))})
(GET "/login" login-form-handler))

/js/app.js would load and compile the boot.cljs and app.cljs scripts
and return the resulting Javascript in one request. If all the
Javascript were done this way, there would be no issue with just
thread local bindings for *macros*. However, if you had some inline
Javascript elsewhere in a different request handler:

(defun login-form-handler []
(let [id (gensym)]
(html
[:div {:id id :title "login"}
[:form {:id "login-form"}
(text-field "login-email" "email")
(password-field "login-password" "password")]
[:p {:id "login-message" :class "message error"} ""]
(jq-let [dlg-id (str "#" id)]
(defn login []
(.text ($ "#login-message") "login submit"))
(defn do-close []
(.dialog ($ this) "close"))
(.dialog ($ dlg-id)
{:autoOpen false
:modal true
:height 230
:buttons {"login" login
"close" do-close}}))])))

The issue is that the clojurejs script within the `jq-let' wouldn't
see any of the macros unless they're loaded again, which can get
expensive on each request. Ideally, the macro definitions would
persist across requests, but in independent namespaces to avoid the
collision issue you brought up. I'd like to think a cheap namespace
implementation would be a way around this (basically, macro expanders
would be kept in a global *namespaces* map with the namespace name as
key). This would have to introduce a `ns' top level form, with at
least support for :use. Or I'm open to other suggestions ...

>
> Another thing that strikes me as a potentially bad idea is the
> reliance on imperative behaviour to generate output, i.e. "emitting"
> or "printing" generated code to a stream instead of returning the
> pieces in a functional way and combining them afterwards. This
> imperative style could hamper your code's composability in the
> future. ... What do others think?

I completely agree. This is one of the bad side-effects (no pun
intended) of the ad-hoc origin of this library. Ideally, there should
be an intermediate representation which captures all of the Javascript
idiosyncracies, and a much simpler emitter. I made the mistake of
assuming the s-expression parse tree *was* that representation
... live and learn. The other factor is that I'm a Common Lisp hack
still getting used to writing idiomatic Clojure :)

> With all that said -- it's actually quite pleasant to work with right
> now. If you're interested in patches, I've done some small
> refactorings to remove duplicate code, added basic docstring support
> and other small fixes. Please see my fork at:
>
> https://github.com/danwerner/clojurejs
>
> Daniel

Thanks very much. I'm glad you were able to use and improve the
code. I like the refactoring you did, as well as the changes to be
more Clojure 1.2. I have to admit, I'm still coming up to speed on
1.2, so that is most appreciated. The docstring feature will help
anyone trying to migrate server side Clojure code to the browser. I
haven't done this before but I believe the way github recommends
merging patches across repos is via a `pull request' [1]. Also, let me
know if you'd like to have write access to the clojurejs repo that I
setup. I'd be happy to add you as a collaborator.

[1] http://help.github.com/pull-requests/

Cheers,

-ram

Olek

unread,
Jan 18, 2011, 5:55:40 PM1/18/11
to Clojure
Nice work,

maybe something like GWT but written in Clojure, will knock to ours
doors.

rob levy

unread,
Jan 18, 2011, 7:35:56 PM1/18/11
to clo...@googlegroups.com

I've just released a stable version of `clojurejs' -- an
unimaginatively named Clojure library for translating a Clojure subset
language to Javascript.


Unimaginative could be a good thing in this case, I was thinking maybe CloJSure, and then someone could invert  abstractions and sacreligiously construct an object system and call it it CLOSjure to top off the the confusion.
Reply all
Reply to author
Forward
0 new messages