Add :update applet argument for functional equivalent of :draw function. #19

Closed
wants to merge 2 commits into
from

5 participants

@technomancy

A shot at implementing what we discussed on IRC.

Also needs to accept initial :state as an argument, though it looks
like that's used for other things I don't entirely understand.

@technomancy technomancy Add :update applet argument for functional equivalent of :draw function.
Also needs to accept initial :state as an argument, though it looks
like that's used for other things I don't entirely understand.
ec6f024
@samaaron

Hey Phil,

looks like a great start. One thing to consider is how this would work with the current state functionality. Perhaps it might make sense to keep it different? It's worth thinking about.

Currently, you can set the state with 'set-state! (which may only be called once to stop it being used as a mutator). This is typically done in the sketch's :setup fn:

(set-state! :foo 1 :bar (atom true))

Once the state has been state, may then be retrieved by calling state and passing the keyword of the state val you'd like to read. i.e. to get the atom associated with the key bar you'd do:

(state :bar)

This would typically be used in the :draw fn.

This means, if you want mutating stuff, you need to store them in refs thus preserving Clojure's nice identity mutation semantics.

With your approach, you probably wouldn't store stuff in refs, rather you'd just keep replacing vals in a similar way to using loop. I therefore wonder whether we need to explicitly store anything with this approach - i.e. it's orthogonal to the 'state' stuff.

Interested in your thoughts...

@technomancy

Thanks for the clarification. I had the nagging feeling I was missing something re: state.

The approach of working with individual fields and hiding the overall map strikes me as odd; was that something intentional or just something quil inherited from clj-processing? It seems like the current set-state! defn and friends don't get you much over just a top-level atom def in your sketch namespace. But I understand needing to not introduce breaking changes, so I can switch to making :update use its own independent atom.

With your approach, you probably wouldn't store stuff in refs, rather you'd just keep replacing vals in a similar way to using loop

I think of it as being more like reduce since it's the return value that's used. Anyway, the only reason it's accepted as an argument to applet is so that an initial value can be specified; the change always comes from the :update function. Maybe :init would be a better name for that?

@samaaron

What set-state! buys you over a top-level atom def in the sketch namespace is sketch local state i.e. it's not shared across sketches. One of the first traps I fell into when playing with clj-processing was running multiple sketches which shared the same atom and wondering why things weren't working as expected.

:init seems like a good name. My only concern at this stage is to try and not get the two state handling options confused. It's not clear to me which is going to be the most elegant solution and I think it's great to experiment. However, I think we need to be careful to not make things more confusing. Perhaps :seed or :update-seed are other names to consider.

@technomancy

Yeah, I'd expect to just close over an atom if I wanted sketch-local state though. It's easy enough to do and doesn't require learning anything specific to the quil API. But it does sound like we want something other than the existing :state field, so I've switched the update-fn to use :init instead.

@technomancy

I feel now that :init might be the wrong thing. Wouldn't it be better to use the return value of the :setup function instead?

@samaaron

Yeah, that definitely makes a lot more sense. I'll look into it when I get a few spare non-baby cycles :-)

@technomancy

If the draw function can be expressed as a pure function, certain time-based constraints can be implemented as higher-order "quil middlewares", such as looping or a sketch that runs for only N seconds. In addition if even things like the height and width are moved to function arguments, middleware which composes multiple sketches together would be easy to implement.

(Leaving this note to myself as a comment here because I'm more likely to remember it if it's here. But of course if anyone wants to run with the idea before I get a chance please do.)

@samaaron

Sounds extremely sane.

My current plan is to look at major changes like this when I start working on Quil 2.0 which will support Processing 2.0 when it's stable. I think we should leave Quil 1.x.y with the current API for stability purposes.

Any prototypes of ideas would be great to see in the meantime though...

@nbeloglazov
Quil member

So user can use either update (update and draw something) or only draw (draw something)?

Did you consider (may be in IRC) using update function as only update? So it doesn't have sideeffects like drawing and should only update current state of the world. And draw function only draws this state without modifying it. This way there is a separation between logic and presentation. Every turn first update called and then draw called.

@gtrak

I have a similar idea in a demo implementation, but it's a bit flaky sometimes. I think Processing isn't expecting to be used that way somehow: https://github.com/gtrak/quilltest/blob/master/src/quilltest/core.clj#L35

@nbeloglazov
Quil member

Functional mode was added in 2.1.0. See wiki article. Thanks for inspiration!

@ysmolsky

This looks real awesome! It reminds me of Racket's big-bang and a step forward functional style of Clojure.

As a side-note I might suggest to add feature to decouple update function from draw function for example when update function does not need to perform with the same rate as draw. So one would end up with updates per second parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment