Debounce and Throttle - leonoel/missionary GitHub Wiki

Debounce and throttle are popular techniques to deal with events happening in bursts. Events in a burst are sometimes related and redundant, therefore it is acceptable to discard some of them in order to save resources.

This discrete input flow produces successive integers in 5 bursts of 5.

(def input
  (->> (m/ap (m/? (m/sleep (m/?> (m/seed (cycle [100 10 10 10 10]))))))
       (m/eduction (take 25) (map-indexed (fn [i _] i)))))

Debounce delays the propagation of each event by a fixed duration, in order to detect a burst. When the burst is over, the last event of the burst is emitted.

Throttle also ensures a minimal duration between events, but emits the first event of the burst immediately and discards after.

input    ----------XXXXX----------XXXXX----------XXXXX----------XXXXX----------XXXXX-------
debounce --------------------X--------------X--------------X--------------X--------------X-
throttle ----------X-----X--------X-----X--------X-----X--------X-----X--------X-----X-----

Debounce

Debouncing is a special case of switching, the continuation is a sleep where the cancellation error is ignored.

(import '(missionary Cancelled))

(defn debounce [dur >in]
  (m/ap
    (let [x (m/?< >in)]
      (try (m/? (m/sleep dur x))
           (catch Cancelled e
             (m/amb))))))
(tests
  (m/? (->> input
         (debounce 50)
         (m/reduce conj)))
  := [4 9 14 19 24])

Throttle

Throttle is essentially about backpressure management, it can be implemented with a relieve discarding oldest values followed by an ap to enforce a minimal delay between successive events.

(defn throttle [dur >in]
  (m/ap
    (let [x (m/?> (m/relieve {} >in))]
      (m/amb x (do (m/? (m/sleep dur)) (m/amb))))))
(tests
  (m/? (->> input
         (throttle 50)
         (m/reduce conj)))
  := [0 4 5 9 10 14 15 19 20 24])