I am new to Clojure, and am beginning to experiment with building an application.
So far, everything I've seen about tutorials on compiling Clojure programs involves interactivity. For example, "load up the REPL and type (load-file "this-or-that") to run. This is fine, but it's not enough.
I am so used to the edit-compile-run idioms of languages like C or Delphi, that I am instinctively driven to make edits, then hit "M-x compile".
The problem is that "lein uberjar", which I understand is the equivalent to "make", is painfully slow to execute even for a hello world. So I'm going to have to figure out how this "interactive development" stuff works, stop using the uberjar like it's quick make, and save it only for the end of the day.
Another thing I noticed while building (using lein uberjar) is that the small GUI app I am working on pops up frames in the compilation process, as if they are executing while compiling. It just seems a bit counterintuitive to me; it is not quite as analogous to "make" as I had thought.
I know the Lisp way of developing things is interactively working in the REPL, and I am not trying to change that: I would like to adapt to this way of life. Unfortunately, I have seen little in the form of documentation on how to do so. For instance, how to reset the current state of the machine. It just seems kind of messy to just keep compiling individual snippets on the fly without being able to do some sort of reset.
Most tutorials I have seen on Clojure (and Lisp) in general seem to focus on hacking in the REPL. Best practices on the deployment of applications remains a mystery to me. My users are just going to be users; they are not going to be developers that are going to load files into a REPL.
So here is my question: any resources for good information or tutorials on the entire process of building a Clojure application, including deployment?
(Note: I have all of the prerequisites installed and working (e.g. Emacs, Slime, Leiningen, etc.), so this is not a question about that).
Best Answer
A couple of quick hints, then some links:
Don't use
lein uberjar
during development; preferlein jar
. The difference is thatlein uberjar
puts all your dependencies in the generatedjar
(including Clojure itself), so that your single jar is an entirely self contained package with your app inside;lein jar
only jars your own code. Theuberjar
approach has obvious benefits for deployment, but for development, you should be able to just use the appropriate classpath when running your app, saving yourself the time necessary to prepare an uberjar. If you don't want to hand-manage the classpath for test runs, check out thelein run
plugin.Also, most likely the majority of your code should not actually be AOT compiled. AOT is necessary in some Java interop scenarios, but most of the time it brings one a slight boost in startup speed and annoying problems with binary compatibility with different releases of Clojure. I suppose the latter issue is not relevant to an
uberjar
-ed standalone app kind of project, but any library code at least should be left to be JIT-ed if at all possible. With Leiningen, you can put a:namespaces
clause in thedefproject
form inproject.clj
to determine which namespaces are to be compiled; whatever you leave out will currently be JIT-ed by default. Older versions of Leiningen used to compile everything by default, which is actually a good reason to upgrade!As for the windows popping out during compilation, I would guess that you're either running window-out-popping code during macro expansion time or outside of any function definition or similar construct. (Something like a
(println "Foo!")
at the top level.) That's just something you shouldn't do, I suppose -- unless you are planning to run your code as a script, anyway. To avoid the problem, wrap side-effecting code up in function definitions and provide an entry point to your application using the:main
clause inproject.clj
. (If you say:main foo
, then the-main
function from thefoo
namespace will be used as the entry point to your app. That's the default, anyway, and at least the above mentionedlein run
seems to have the name hardcoded -- not sure about lein itself.)As for resetting the state of the REPL -- you can just restart it. With SLIME, M-x slime-restart-inferior-lisp will do just that while maintaining all other state of your Emacs session.
See also these discussions on the Clojure Google group: