Help with atom

27 views
Skip to first unread message

Tom Oinn

unread,
Feb 15, 2012, 11:55:30 AM2/15/12
to cam...@googlegroups.com
Hi,

I'm trying to write a debounce function. The intent is to wrap an
arbitrary function such that an invocation within x milliseconds of a
previous invocation will return nil (not calling the wrapped function)
and will otherwise call the wrapped function and return its value. I
can't get this to work, I assume I'm doing something dense with the
atom I'm using to store the last invocation time and binding it at the
wrong point but I can't work out where?

(defn debounce
"Wrap a function such that calls within 'timeout' milliseconds of a
previous call
are ignored and return nil, other calls call the wrapped function
and return its result"
[timeout f]
(let [last-invocation-atom (atom 0)]
(fn [& args]
(let [current-time (System/currentTimeMillis)
last-invocation @last-invocation-atom]
(reset! last-invocation-atom current-time)
(if (< (- current-time last-invocation) timeout)
nil
(apply f args))))))

I thought this would return a closure over the atom, but repeated
calls of the returned function always have value of the atom as it's
initial value 0, so the reset! call isn't doing what I hope or the
atom is being re-created each call. What am I doing wrong?

Tom

Sam Aaron

unread,
Feb 15, 2012, 12:19:09 PM2/15/12
to cam...@googlegroups.com
Hey Tom,

I can't see the bug you're describing - it looks good from my REPL. Could you give me details about what you expected to see and what you saw with specific inputs?

The only thing I saw was that you're always comparing with the "last invocation" rather than the "last successful invocation". This means if you thrash your fn, it will *never* be called. I assume you want it to be called at most 1 time per timeout ms?

This slight re-ordering of things did it for me:

(defn debounce
"Wrap a function such that calls within 'timeout' milliseconds of a
previous call
are ignored and return nil, other calls call the wrapped function
and return its result"
[timeout f]
(let [last-invocation-atom (atom 0)]
(fn [& args]
(let [current-time (System/currentTimeMillis)
last-invocation @last-invocation-atom]

(if (< (- current-time last-invocation) timeout)

(println "too quick!")
(do
(reset! last-invocation-atom current-time)
(apply f args))
)))))

Really nice idea though. I can totally imagine using something like this smoothing out OSC streams. I'd probably store the last value though, and return that instead of nil. The value would then only appear to change every timeout ms at most.

Sam

---
http://sam.aaron.name

Tom Oinn

unread,
Feb 15, 2012, 12:48:54 PM2/15/12
to cam...@googlegroups.com
On 15 February 2012 17:19, Sam Aaron <sama...@gmail.com> wrote:
> Hey Tom,
>
> I can't see the bug you're describing - it looks good from my REPL. Could you give me details about what you expected to see and what you saw with specific inputs?

I'm calling it like this -

(defn handler [{[message-name & message] :args}]
(let [debounced-parse-topology (debounce 1000 parse-topology)
debounced-parse-topology2d (debounce 1000 parse-topology2d)]
(case message-name
"topology_update" ; {"topology_update",count,cubeA,faceA,cubeB,faceB....}
(if-let [topology (debounced-parse-topology message)]
(println (stringify-topology-list topology)))
"topology_2D" ; Summary of implied cube positions and rotation
from topology information
(if-let [topology (debounced-parse-topology2d message)]
(println topology)).....

So trying to create and use a debounced version of the parser
functions then only print out the results if non-nil, but this always
passes down the 'cache miss' path? (this is a handler function passed
to osc-listen)

> The only thing I saw was that you're always comparing with the "last invocation" rather than the "last successful invocation". This means if you thrash your fn, it will *never* be called. I assume you want it to be called at most 1 time per timeout ms?

Yup, that would make sense.

Tom

Sam Aaron

unread,
Feb 15, 2012, 2:19:49 PM2/15/12
to cam...@googlegroups.com
Hi Tom,

if you're registering this handler to be called by an osc-server, then I can see why it's not working as expected. Every time the handler is called, the let bindings are evaluated which creates a new debounced version of your fns parse-topology and parse-topology2d. This means you're hitting newly created debounce fns on every new OSC message received.

Instead, you should create your debounce fns once, and pass references to them. This is easily achieved with def at the top level:

(def debounced-parse-topology (debounce 1000 parse-topology))

then just use #'debounced-parse-topology in your hander fn and remove the top level let binding.

Let me know if that helps,

Sam

---
http://sam.aaron.name

Tom Oinn

unread,
Feb 15, 2012, 6:45:07 PM2/15/12
to cam...@googlegroups.com
On 15 February 2012 19:19, Sam Aaron <sama...@gmail.com> wrote:
> Hi Tom,
>
> if you're registering this handler to be called by an osc-server, then I can see why it's not working as expected. Every time the handler is called, the let bindings are evaluated which creates a new debounced version of your fns parse-topology and parse-topology2d. This means you're hitting newly created debounce fns on every new OSC message received.

Doh! As usual, looking for bugs in the wrong bit of code. On the plus
side, apparently I do understand how atoms work :)

> Instead, you should create your debounce fns once, and pass references to them. This is easily achieved with def at the top level:
>
> (def debounced-parse-topology (debounce 1000 parse-topology))
>
> then just use #'debounced-parse-topology in your hander fn and remove the top level let binding.

Yup - what does the #' do? Still getting my head around the clojure
syntax, especially the reader macros. As far as I know you can just
refer to it by name?

Tom

Sam Aaron

unread,
Feb 15, 2012, 6:52:11 PM2/15/12
to cam...@googlegroups.com

On 15 Feb 2012, at 23:45, Tom Oinn wrote:
>
> Yup - what does the #' do? Still getting my head around the clojure
> syntax, especially the reader macros. As far as I know you can just
> refer to it by name?

#' allows you to refer to a var, not the automatically dereferenced contents of the var. You occasionally see it used to separate Clojure vars from regular prose - which was all I was doing there. It also has the benefit of not being automatically inlined, allowing you further opportunities to redefine the contents and have the new definition have immediate effect.

Sam

---
http://sam.aaron.name


Tom Oinn

unread,
Feb 16, 2012, 7:39:13 AM2/16/12
to cam...@googlegroups.com

Makes sense - so vars have an implicit equivalent to the deref
operator used with atoms, agents and refs, and need an explicit
reference operator?

Anyway, thanks for the help, I believe the code at
https://gist.github.com/1754890 now works, although I need to do some
proper testing. This should probably be on the overtone list, but does
that API look sane for connecting to the cubes? I think I might try
getting the synth engine running and hooking the sensor values into
the control functions when I get home :)

Tom

Reply all
Reply to author
Forward
0 new messages