ruricolist / serapeum

Utilities beyond Alexandria
MIT License
420 stars 41 forks source link

Closure that depletes a number? #93

Closed lukego closed 3 years ago

lukego commented 3 years ago

I wrote this code today and I feel that it's missing an abstraction:

(defun weighted-index (weights &aux (roll (random (float (sum weights)))))
  "Choose an index of WEIGHTS by weighted random choice."
  (count-if (op (plusp (decf roll _))) weights))

Specifically the (op ...) form seems a bit ad-hoc and I wonder if it could be replaced by a call to some counterpart of (distinct) that returns true until a quantity has been depleted. Something like

(defun depleting (n)
  (lambda (x) (plusp (decf n x)))

but I find myself in doubt and whether depleting is the right name and whether it should be generalized in some way e.g. parameterized with an alternative updating function (in which case it could almost be called accumulating...)

Any ideas? Should I just tuck it into my own private library and see how often the pattern pops up in future? Or is there a better way to formulate that whole function?

ruricolist commented 3 years ago

I could imagine this being useful for debugging (by analogy with "optimization fuel" in compilers).

lukego commented 3 years ago

Here's a possible formulation under the evocative name fuel.

(defun fuel (level)
  "Return a function that tracks 'fuel' consumption.

The function takes one argument and subtracts its value from the
current fuel level.

The two return values are a boolean indicating whether the available
fuel has been exceeded followed by the current fuel level (which may
be negative.)"
  (lambda (consumption)
    (let ((remaining (decf level consumption)))
      (values (>= remaining 0) remaining))))
SERAPEUM> (fuel 10)
#<FUNCTION (LAMBDA (CONSUMPTION) :IN FUEL) {1009B2482B}>
SERAPEUM> (funcall * 2)
T
8
SERAPEUM> (funcall ** 0.1)
T
7.9
SERAPEUM> (funcall *** 100)
NIL
-92.1

SERAPEUM> (drop-while (fuel 10) '(1 2 3 4 5 6))
(5 6)
lukego commented 3 years ago

I've adopted this fuel function in my local copy of Serapeum now. I'll create a pull request in the future if I actually find recurring uses. Meanwhile I'll close this issue and say thanks for the feedback!