More functional Quil

373 views
Skip to first unread message

J. Pablo Fernández

unread,
Mar 9, 2014, 8:47:57 AM3/9/14
to clo...@googlegroups.com
Hello Clojurians,

I'm starting to play with Processing and I want to use my favorite programming language for it, so I started using Quil. The problem is that Processing has a imperative architecture and makes my Clojure code look bad. For those that don't know Processing/Quil, here's a little overview that's relevant to this email.

Processing is a language and a library that makes it easy to draw on the screen to do generative art, simulations, etc. Processing programs have two methods, a setup method that is run once, at the beginning, and then a draw method that is run over and over. Literally something like:

Walker w;

void setup() {
  w = new Walker();
}

void draw() {
  w.render();
  w.walk();
}

I'm using a hypothetical walker, an entity that walks around the screen. More or less taken from here: http://natureofcode.com/book/introduction/

My understanding is that the equivalent Clojure code using Quil would look like this:

(defn setup []
  (set-state! :walker (atom (create-walker))))

(defn draw []
  (render-walker @(state :walker))
  (swap! (state :walker) walk))

Quil stores a map in the meta-data of the Processing applet and let's you access it. Since I need the state to change, I'm storing the walker in an atom. I found examples out there following more or less this structure. I'm not happy with this structure, it doesn't feel functional enough.

When thinking about this, I got reminded of Erlang processes, specially with OTP, where you have a function that's called over and over to which the state is passed and which returns the next state. Following that structure I would like to implement it like this:

(defn setup []
  [(create-walker)])

(defn draw [walker]
  (render-walker walker)
  [(walk walker)])

The result of calling setup is a vector containing the attributes to draw, and the result of draw is a vector containing the attributes that will be used in the next call to draw.

Does this make sense? Does it look cleaner to you guys?

I achieved that by writing this wrappers for setup and draw:

(defn setup-wrapper []
  (set-state! :state (atom (setup))))

(defn draw-wrapper []
  (swap! (state :state)
         (fn [s] (apply draw s))))

My concern with this is that now the whole state is in one single atom that I'm replacing on each frame and that might not be good. Is it copying the whole state or can Clojure use its copy-on-demand feature even for atoms? Is there a better way of doing this?

Thanks.

Laurent PETIT

unread,
Mar 9, 2014, 9:02:52 AM3/9/14
to clo...@googlegroups.com
Hello, 

To be honest I don't see any fundamental difference between your first attempt and the improvement: both share the fact that the mutation of the state happens within the draw function. So in both cases, you have a temporal coupling between updating the state of the app and rendering a new view of the app's state.

I would suggest that you don't swap! at all within draw, just deref and render the result of the dereffing.

And, in another thread, at potentially a totally different pace than the redrawing's pace, update the application's state accordingly to business rules / constraints.

Schematically, something like this: 

(def app-state (atom (init-state)))

(defn draw [...]
  (let [app-snapshot (deref app-state)]
      ... call quil primitives to render the application state snapshot ...))

(future
  ... logic which updates the app-state atom depending on business rules / constraints, in a separate thread ...)


HTH,

-- 
Laurent



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

J. Pablo Fernández

unread,
Mar 9, 2014, 9:21:58 AM3/9/14
to clo...@googlegroups.com


On Sunday, March 9, 2014 1:02:52 PM UTC, Laurent PETIT wrote:
Hello, 

To be honest I don't see any fundamental difference between your first attempt and the improvement: both share the fact that the mutation of the state happens within the draw function. So in both cases, you have a temporal coupling between updating the state of the app and rendering a new view of the app's state.

Yes, what's happening in both cases is very similar, but the function draw in the "functional" style, in my opinion, is easier to read and maybe it's also easier to test.
 
I would suggest that you don't swap! at all within draw, just deref and render the result of the dereffing.

And, in another thread, at potentially a totally different pace than the redrawing's pace, update the application's state accordingly to business rules / constraints.

Schematically, something like this: 

(def app-state (atom (init-state)))

(defn draw [...]
  (let [app-snapshot (deref app-state)]
      ... call quil primitives to render the application state snapshot ...))

(future
  ... logic which updates the app-state atom depending on business rules / constraints, in a separate thread ...)

I never worked with future, this is exciting, but I have some questions. Do you mean that future is completely separate from draw? I'm just getting started, but draw is not only a function to draw, but as a side effect is the clock of the app, as it's called according to the set frames per second and you normally *take a step* en each draw. Would draw create these futures for the next draw?

Laurent PETIT

unread,
Mar 9, 2014, 9:27:48 AM3/9/14
to clo...@googlegroups.com
future is just a detail in what I was trying to explain.
If your logic ties the frame rate and the stepping function, then forget about what I was suggesting. You just will have to ensure that all your logic+rendering time is below the app's clock rate, of course.

Nikita Beloglazov

unread,
Mar 9, 2014, 8:29:00 PM3/9/14
to clo...@googlegroups.com
Hi Pablo

You can find similar old thread on Quil github repo: https://github.com/quil/quil/pull/19 It may serve as good background what other people considered to make Quil more "functional-style".

I like your suggestion though I would split your :draw function to 2 fns: an :update function, which only purpose is to update state and :draw which draws state and doesn't change the world at all. If this approach is implemented - other handler functions like :mouse-move, :key-pressed are also need to become update-like functions - they should take state as argument and return new state.

The only problem is that it is not backward compatible at all. But probably we still can do it... We can add option :fun-mode? true (stands for functional-mode) which enables all these changes - :draw takes state as argument, new :update function is added for modifying state, all handlers behave like :update. This option is enabled per-sketch. It requires additional work on Quil internals, but I think it is doable. This option can be implemented in coming quil 2.0 and it would be great feature to have.

One more thing we could do to make it more functional-like - pass "changed" values to handlers directly. Currently when :key-pressed handler is called - no argument is passed to the function and you need to use (key-code) or (raw-key) functions to identify which key was pressed. I think this parameters should be explicitly passed to the function.

What do you think?

Nikita

Lee Spector

unread,
Mar 9, 2014, 10:16:25 PM3/9/14
to clo...@googlegroups.com

FWIW I'm not crazy about these suggestions because they seem to me to be mostly cosmetic, and actually negative if they end up leading to multiple incompatible modes of operation. The Processing model seems to me to be intrinsically imperative, and it's also well-known by lots of people and easy to understand. The current Quil scheme, which provides fairly direct access to Processing's existing model from Clojure, still allows us to write functional-style code for all of the interesting stuff that we do within/between calls to the imperative Processing calls. And it allows one to translate Processing ideas into Quil relatively easily. So I like it like it is :-).

-Lee

Gary Trakhman

unread,
Mar 10, 2014, 12:27:40 AM3/10/14
to clo...@googlegroups.com
FWIW, I've got an example of a decoupled update/draw loop here: https://github.com/gtrak/quilltest/blob/master/src/quilltest/balls.clj


Nikita Beloglazov

unread,
Mar 10, 2014, 1:27:25 PM3/10/14
to clo...@googlegroups.com
Lee, 

I think question about managing/updating state in quil pop ups pretty frequently and many people expect quil to have built-in tools to do it in functional style. And using atoms to update state may feel hacky. I think this tools should exist and quil 2.0 is a good time to address the issue. Yes, Pablo's and mine changes are cosmetic but still they will help to feel functional nature of quil and I think it is a win. 
As for translating Processing code to Quil - I think it's already not very easy for people who has little experience with functional programming and therefore I don't think these cosmetic changes will add much complexity.

The only issue I see, which is you mentioned, is having two similar but still separate modes. It may create confusion for newcomers.  

Though I will be glad to hear other opinions on the question and see if people generally like the idea or would rather keep old good quil as it is.


Gary, 
Thank you. It might be useful in future if we decide to implement similar thing.


Thank you,
Nikita


You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/UQ3iPC6Oj9g/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Gary Trakhman

unread,
Mar 10, 2014, 2:11:33 PM3/10/14
to clo...@googlegroups.com
The complicated-looking semaphore code was adapted from http://stackoverflow.com/a/5275254/2559313 . I think some issues with other alternatives are in there.

J. Pablo Fernández

unread,
Mar 19, 2014, 3:13:09 PM3/19/14
to clo...@googlegroups.com
Hello Nikita,

I think Quil should definitely provide a way to write functional style processing code. At the moment, my knowledge of both Clojure and Processing is not enough to evaluate these solutions. I'm certainly going to write my code following some of this and if someone proposes a solution to be part of Quil 2, I would happily adopt it and report back on my experiences.

BTW, I noticed you had some discussions on IRC, you can find me there as Pupeno.
Reply all
Reply to author
Forward
0 new messages