Saturday, December 13, 2008

Clojure App dev, step 1 -> repl-QT-repl-QT

The goal of this step is to ensure you can start a qt frame from the repl, switch to qt's event handling (thus leaving the repl) but get back to the repl using a button.

This is critically important; I need to be able to run the application and then update it dynamically *without* restarting anything. I can't tell you how many times, while debugging something like mouse picking or moving objects around in 3d space I had to break into the debugger, then check out what is going wrong. Next I would make an attempt to fix, recompile, reload the presentation and then repeat. Over and over and over and fucking over again. And again. What I would like is that I can see a given function isn't working correctly; I just update the function definition *while I am running the editor*. This is goddamn important. With this working complex, really useable features are much easier to get working correctly.

OK, so here we go!

It should look like:

(start from repl)
open qt frame with one button, return to repl.
switch to qt's event handling thus effectively leaving the repl
push a button and return to repl *without* closing frame
add a menu item to the existing frame, thus ensuring we can mutate the datastructure and get a good update
exit main frame; thus going back to repl.
see what the state of the main frame is; we may be able to open it

This relies on one key assumption: that I can exit qt's event handing without closing the window. I believe I can but I don't know.

If this assumption doesn't hold then I can always store, latently, the commands for creating the UI. Then when I want to repl around with shit I can destroy the entire UI, update the commands to add new elements, and re-create. This is heavy-handed and rude but it might work. It will remove perhaps 30-40% of the functionality that want, however so I will really try to avoid this.

Reading the QT documentation, it looks like you call QApplicationCore.exec() to get things going and then you can call QApplicationCore.exit() in order to return from the event loop. So your return-to-repl button should just call exit, perhaps. There is also a processEvents call that you could call. Essentially, you could loop over calling process events and have your button set a global that tells you to stop. But if exit works then that is the sweetest.

Simple clojure QT example

This should get me started. There is something really weird when you are learning a new language. You don't know how to create a hash, or vector, and it seems every character you type is wrong. It takes a couple days before you (or perhaps just I) get things going. On the other hand, this level of comfortability with the language has benefits. You focus far more on algorithm than details; this leads to better code.

For example, if you lived in C for all your life and was asked to do something one of the first things you would visualize or plan out would be the memory access system because the most complex part of most C programs is based around memory handling; at least as much as what the program is meant to do. It takes a few other languages with garbage collection before you begin to think at a higher level and then work down to the memory level if you have to.

Side tracked:

BillC figured this all out first
This too. It hasn't worked for me yet, though

Well, this blows. I have been trying to diagnose an error for quite some time now, it looks like:

[Thrown class java.lang.ExceptionInInitializerError]

0: com.trolltech.qt.QtJambiObject.(QtJambiObject.java:57)
1: java.lang.Class.forName0(Native Method)
2: java.lang.Class.forName(Class.java:169)

Checking shit out, it isn't immediately clear what is going on. Time to research how to debug this error....

The demos run just fine, so I know it is possible to run applications. Trolltech -helpfully- included a binary starter to the demos so I can't easily see what is going on.

Holy shit that hurt!

Why God Why Doesn't It Work?!?1!1?

**Don't use JDK 6**

Nice, 2 hours of fucking around with java madness. I finally figured out the magic google code that would find the problem exactly:

java.lang.UnsatisfiedLinkError libQtCore.4.dylib

So I need to amend my other post.

Some more things are working. It appears that QApplication.exit actually closes all open widgets. This isn't exactly what I want. So I will just call process events from a loop and have a button set a variable to break out of the loop.

Thus I would like to be able to define a variable in the namespace that is something like "process_events_running" and have the single button in my application set it to false. Then I will provide a custom exec function that calls process in a loop checking that variable.

The simples way to do this would be a closure. I don't immediately see how to do this, but I remember rich hickey stating that lambdas are "ICallable" or something like that. I know that the signal system in QTJambi uses reflection, so if I just pass in a closure as the "this" argument to connect, and pass in the function named call on the icallable I may be able to get somewhere. The interface is Callable:

user> (lambda `(println "hello"))
; Evaluation aborted.
user> (fn [] (println "hello"))
#
user> (set x (fn [] (println "hello")))
; Evaluation aborted.
user> (def x (fn [] (println "hello")))
#'user/x
user> (. x call)
hello
nil
user> x
#
user> (x)
hello
nil
user>

Still not finished, but I need some food.

OK, nice microwave pizza and I am back.

So, passing in a closure to connect works fine. I do it like this:

(defn exec []
(def exec_var 1)
(while
(== exec_var 1)
(QApplication/processEvents)))

(defn create_app_frame []
(ensure_app_init)
(let [app (QApplication/instance)
button (new QPushButton "Go Clojure Go")]
(.. button clicked (connect (fn [] (def exec_var 0)) "call()"))
(doto button
(.resize 250 100)
(.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
(.setWindowTitle "Go Clojure Go")
(.show))
button)) ;return the button for further reference

Now I need things to sleep because I am chewing up CPU by calling process events over and over again. There is a hasPendingEvents call; so now all I need is how to make the system sleep in a platform independent way. Java has Thread.sleep in the language and that is that.

I didn't mess with the datastructures of the frame yet (mainly because I just have a button). But I have a QT app, running from the repl and most importantly returning to the repl at the push of a button. I had to do a lot of work for this first app; get emacs working, run up against problems with the mac java implementation interacting with QT, and learn a little bit of clojure (which was the best part). The benefits are huge, though, because QT is a good platform to move forward on and because clojure is an order of magnitude more powerful than java; and I personally believe that developing from the repl is much more powerful than developing from a compile/run standpoint.


At this point, I would love to upload all files related to this. I can't, so I started a github project where I will put all the code.
Git R Dun

OK, to quickly review what you will need to get shit working:

QT - I had version 4.4.3_01
java - 1.5.0
aquamacs emacs
git,svn,cvs, and the latest versions of:
(svn from various other places)
clojure
clojure-contrib

(from joshu's git repository)
clojure-mode
swank-clojure

(cvs. I wish this project had some better regression testing systems)
slime

Watch every single presentation here:
Rich Hickey

Watch them again until you really get it.

Next up will be an emacs post; I need to remember how to use/navigate within emacs and how to integrate with slime better.

No comments: