Supporting more functional style sketches: Naïve idea

54 views
Skip to first unread message

J David Eisenberg

unread,
May 30, 2014, 2:17:38 AM5/30/14
to clj-pro...@googlegroups.com
Quick background: I'm a very recent newcomer to functional programming, and am just beginning to learn Clojure. I currently use Processing for the "Introduction to programming" course that I teach, and that is why I'm interested in Quil.

From looking at the concentric circles and growing circles examples at https://github.com/quil/quil-examples/blob/master/src/quil_sketches/gen_art/README.md, it appears that you need a "global" state, and you modify it in the examples via seq->stream or swap!

I think part of the problem is that Processing is doing too much for you -- it's calling (draw []) every 1/frameRate seconds, and you can't pass any parameters to it. If you leave that responsibility to the programmer rather than to Processing's main loop, you could write something like this, which, to my eyes, is a bit more functional:

(defn draw-frame [param-list]
  (let [[cent-x cent-y diam] param-list]
    (when (<= diam 400)
      (background 180)
      (ellipse cent-x cent-y diam diam)
      (Thread/sleep (/ 1000.0 frame-rate))
      (recur [cent-x cent-y (+ 10 diam)]))))


The Thread/sleep is ugly; if it were possible to create a function or macro named recur-after that would combine the sleep and recur, you could write the last two lines as

      (recur-after frame-rate [cent-x cent-y (+ 10 diam)]))))

Some problems with this approach might be:

1) It puts more burden on the programmer
2) It might not be easy to make Processing not do its repeated calls to draw
3) Having the programmer take over the loop is probably antithetical to the Processing philosophy. Would it even be Processing any more?

I am sure there are a thousand other reasons that this is a silly and unworkable idea, but I am fine with you all thinking I am a simpleton if it gets the discussion started.

Максим Карандашов

unread,
Jun 1, 2014, 9:51:55 AM6/1/14
to clj-pro...@googlegroups.com
Hi David!

I have question for you: What kind of problems solved your approach?

Because any code (that you write) should solve some problem. Do you think the same?


P.S. If you want to using a functional approach. You can write a macro that packing and unpacking global state inside the draw function.

But I really don't think that is a good way. Because all is good when you have one, two or three var and all work happend inside draw function.
What happend if you have more that 10 vars? Or you want to change sketch state from other place that draw function?

How do you propose to solve these problems?

P.S.S. You can use reset! function for atoms. Very usefull sometimes :)

Dan Bernier

unread,
Jun 1, 2014, 10:09:13 AM6/1/14
to clj-pro...@googlegroups.com

If the core parts of the sketch are functional, and parameterized appropriately, that can mitigate the trouble from global state. The only parts that depend on it are pushed out to the edge.

I do this with processing sketches all the time, by introducing objects that behave like streams or filters.

That said, I'm no clojure programmer.

Two links that might be useful:
- https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell (paywall, sorry)
- http://natureofcode.com/book/chapter-8-fractals/ this is in a similar spirit, though not quite related...Shiffman talks about generating Koch lines via either a recursive function, or a emrecursive data structure.

--
You received this message because you are subscribed to the Google Groups "clj-processing" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clj-processin...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

J David Eisenberg

unread,
Jun 1, 2014, 2:23:46 PM6/1/14
to clj-pro...@googlegroups.com


On Sunday, June 1, 2014 6:51:55 AM UTC-7, Максим Карандашов wrote:
Hi David!

I have question for you: What kind of problems solved your approach?

Because any code (that you write) should solve some problem. Do you think the same?


P.S. If you want to using a functional approach. You can write a macro that packing and unpacking global state inside the draw function.

But I really don't think that is a good way. Because all is good when you have one, two or three var and all work happend inside draw function.
What happend if you have more that 10 vars? Or you want to change sketch state from other place that draw function?

How do you propose to solve these problems?

I'm not sure how to solve the problems; I was responding to Nikita Beloglazov's comment "Support more functional-style sketches." in the Quil 2.0.0 announcement.

The example I gave earlier isn't really workable - the call to ellipse draws in to the off-screen buffer, but there is nothing in the code that displays the buffer on the screen.  The following may be a better approach:

First, expose the Processing drawing loop and make it callable.

(defn draw-loop [draw-frame-function params]
  (let [new-params (draw-frame-function params)]
    (display-buffer-to-screen)
    (Thread/sleep (/ 1000.0 frame-rate))
    (recur draw-frame-function new-params)))


The person using Quil provides the draw-frame-function and its parameters; draw-frame-function gives the drawing commands for a frame and returns the new parameters to use when called again. The growing circle code might look like this:

(defn growing-circle [param-list]

  (let [[cent-x cent-y diam] param-list]
    (background 180)
    (ellipse cent-x cent-y diam diam)
    (vector cent-x cent-y (if (< diam 400) (+ 10 diam) diam))))

The sketch would then be drawn by calling

(draw-loop growing-circle [(/ (width) 2) (/ (height) 2) 10])

Nikita Beloglazov

unread,
Jun 1, 2014, 4:25:00 PM6/1/14
to clj-pro...@googlegroups.com
Hi David

Thanks for your idea, though it seems to be pretty radical shift from usual quil/processing workflow. And I'm a little bit afraid of partially reimplementing processing as I think it may cause a lot of problems. I would encourage you to experiment with it and if you find it useful for your case - you can prepare pull request or send a link to your repository so we can try it live. Personally for me it looks like a difficult task and I'm not going to invest my time in it right now.

I've had an idea for some time now how to introduce functional-mode to quil and I'm planning to implement/experiment with it in next few days. Main problem (as I see it) with quil non-functional workflow is that you have to handle state in imperative style: on each invocation of draw function you have to update some atom and use updated value to draw something on screen. draw has to do both: update AND draw state. Which doesn't sound right. I want to introduce new update function and change semantics for draw and setup functions a little bit. Here is an example of sketch drawing moving ellipse in new fun-mode:

; new version of setup should return initial state
; you can do whatever you want in it, but result of setup will be used as initial state
; here it returns initial coordinates of the ellipse (x, y) = (0, 0)
(defn setup []
  {:x 0 :y 0})

; new version of draw take state explicitly and draws it
; it should ONLY draw and not change state
(defn draw [state]
  (background 255)
  (ellipse (:x state) (:y state) 100 100))

; update takes old state, updates it and returns new state
; here it increases x coordinate of the ellipse by 3 and y by 2
(defn update [state]
  (-> state
      (update-in [:x] + 3)
      (update-in [:y] + 2)))

(defsketch fun-mode
  :size [500 500]
  :setup setup
  :draw draw
  :update update)

You can see that logic of how state is actually stored is hidden from the user and she/he can ignore it. Semantics for all keyboard, mouse and other handlers will also be changed in similar way.

The problem we'll face when implement this mode that it is not compatible with old workflow (and sketches). To solve it I want to make it optional. By default old non-functional style will be enabled and if you want to use new fun-mode - enable it explicitly.

As a "side-effect" of implementing fun-mode I'm going to introduce middleware to quil. It will be similar to ring middleware if you're familiar with web-development in clojure. It will allow to create plugins for quil to enhance it functionality without actually changing the library itself. fun-mode will be just an middleware which you can enable.

What do you think?

Thanks,
Nikita


--

J David Eisenberg

unread,
Jun 2, 2014, 12:21:04 AM6/2/14
to clj-pro...@googlegroups.com


On Sunday, June 1, 2014 1:25:00 PM UTC-7, Nikita Beloglazov wrote:
Hi David

Thanks for your idea, though it seems to be pretty radical shift from usual quil/processing workflow. And I'm a little bit afraid of partially reimplementing processing as I think it may cause a lot of problems. I would encourage you to experiment with it and if you find it useful for your case - you can prepare pull request or send a link to your repository so we can try it live. Personally for me it looks like a difficult task and I'm not going to invest my time in it right now.

It probably is beyond my abilities with Clojure at the moment.

I've had an idea for some time now how to introduce functional-mode to quil and I'm planning to implement/experiment with it in next few days. Main problem (as I see it) with quil non-functional workflow is that you have to handle state in imperative style: on each invocation of draw function you have to update some atom and use updated value to draw something on screen. draw has to do both: update AND draw state. Which doesn't sound right. I want to introduce new update function and change semantics for draw and setup functions a little bit. Here is an example of sketch drawing moving ellipse in new fun-mode:

; new version of setup should return initial state
; you can do whatever you want in it, but result of setup will be used as initial state
; here it returns initial coordinates of the ellipse (x, y) = (0, 0)
(defn setup []
  {:x 0 :y 0})

; new version of draw take state explicitly and draws it
; it should ONLY draw and not change state
(defn draw [state]
  (background 255)
  (ellipse (:x state) (:y state) 100 100))

; update takes old state, updates it and returns new state
; here it increases x coordinate of the ellipse by 3 and y by 2
(defn update [state]
  (-> state
      (update-in [:x] + 3)
      (update-in [:y] + 2)))

(defsketch fun-mode
  :size [500 500]
  :setup setup
  :draw draw
  :update update)

You can see that logic of how state is actually stored is hidden from the user and she/he can ignore it. Semantics for all keyboard, mouse and other handlers will also be changed in similar way.

It seems that you'll have to change part of how Processing works; the PApplet's draw() function doesn't accept any arguments, as far as I can tell. I'm also not sure how PApplet will find out about the update function.But it seems like a problem that is possible to solve.

The problem we'll face when implement this mode that it is not compatible with old workflow (and sketches). To solve it I want to make it optional. By default old non-functional style will be enabled and if you want to use new fun-mode - enable it explicitly.

As a "side-effect" of implementing fun-mode I'm going to introduce middleware to quil. It will be similar to ring middleware if you're familiar with web-development in clojure. It will allow to create plugins for quil to enhance it functionality without actually changing the library itself. fun-mode will be just an middleware which you can enable.

 Once I see the code, I think I'll understand it better :)

Reply all
Reply to author
Forward
0 new messages