Debouncing the Strike: Filtering Noise from Signal
A praying mantis doesn’t lunge at every flicker of movement. Wind shifts a leaf, shadows cross the terrarium glass, condensation drips—the mantis stays motionless. But when a fruit fly lands and stays visible for half a second, the strike happens in 30 milliseconds. The mantis has a built-in debounce filter: ignore transients, wait for the signal to stabilize, then commit.
In electronics, mechanical switches bounce. Press a button and the contacts make-break-make-break dozens of times in the first 10 milliseconds before settling. Early microcontrollers saw each bounce as a separate press. The solution was debouncing: read the switch, wait a settling period (usually 20-50ms), read again. Only count it if both reads agree.
The Perl approach is procedural and explicit. Track the last change time, ignore any event within the debounce window:
use Time::HiRes qw(time);
my ($last_event, $debounce_ms) = (0, 50);
sub time_ms { int(time() * 1000) }
sub trigger_action { print "Strike!\n" }
sub on_input {
my $now = time_ms();
return if ($now - $last_event) < $debounce_ms;
$last_event = $now;
trigger_action();
}
Swift can express the same idea with closure-based state and timer dispatch:
import Dispatch
func debounce(ms: Int, action: @escaping () -> Void) -> () -> Void {
var timer: DispatchWorkItem?
return {
timer?.cancel()
timer = DispatchWorkItem { action() }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(ms), execute: timer!)
}
}
let debouncedStrike = debounce(ms: 50) { print("Strike!") }
The mantis version: wait 500ms of continuous visual lock before committing metabolic energy to a strike. False positives waste calories. False negatives just mean the next fly. The threshold is tuned by evolution the same way we tuned capacitors and resistor values on debounce circuits.