Skip to content

Commit 1fcabdf

Browse files
committed
with-accumulators is better, bug fixed
There is a new 'by' option which allows a step with no optional argument at all.
1 parent b27225b commit 1fcabdf

File tree

4 files changed

+87
-44
lines changed

4 files changed

+87
-44
lines changed

README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,13 @@ The simple form is `(accumulator operatior &optional initially)`.
133133
- `operator` is the operator corresponding to the accumulator: this denotes a function which can take either two or no arguments: it is called with no arguments to initialise the variable underlying the accumulator if there is no `initially` value (this is the only case it is called with no arguments), or with the current value of the accumulator and the thing to be accumulated when the local function is called to accumulate something.
134134
- `initially`, if given, is the initial value. If it is not given the accumulator is initialised by calling the operator function with no arguments.
135135

136-
The more general form is `(accumulator operator &key initially type returner)`.
136+
The more general form is `(accumulator operator &key initially type returner default by)`.
137137

138138
- `accumulator`, `operator` and `initially` are the same as before.
139139
- `type` is a type specification which is used to declare the type of the underlying variable.
140140
- `returner` denotes a function of one argument which, if given, is called with the final value of the accumulator: its return value is used instead of the value of the accumulator.
141141
- `default`, if given, causes the accumulator function to have a single optional argument for which this expression provides the default value.
142+
- `by`, if given, means that the accumulator takes no arguments and steps the value by `by`. `by` and `default` can't both be given.
142143
- There may be additional keyword arguments in future.
143144

144145
An example: let's say you want to walk some cons tree counting symbols:
@@ -196,6 +197,16 @@ There is no single-accumulator special case: it didn't seem useful as you need t
196197

197198
The accumulation operator and returner are *names* which denote functions, not functions: they can be either symbols or a lambda expressions, they can't be functions. Specifically it needs to be the case that `(operator ...)` is a valid form. That means that if you do want to use some function you'd need to provide an operator which was, for instance `(lambda (into val) (funcall f into val))`.
198199

200+
Both `by` and `default` are forms which are evaluated at call time in the current lexical and dynamic environment (they're simply the defaults for arguments). So this will return `3`:
201+
202+
```lisp
203+
(let ((by 1))
204+
(with-accumulators ((a + :by by))
205+
(a)
206+
(setf by 2)
207+
(a)))
208+
```
209+
199210
`with-accumulators` is very much newer than either of the other two macros, and may be more buggy. It is certainly the case that new keywords may appear in accumulator specifications.
200211

201212
`with-accumulators` can implement `collecting` or `with-collectors`:

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.6.0
1+
8.7.0

collecting.lisp

+65-42
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,14 @@ the argument to the accumulator function. Its return value is the new
136136
value of the accumulator.
137137
138138
The extensible specification is (name operator &key initially type
139-
returner). In this case name, operator & initially mean exactly the
140-
same as previously, but type is a type specification for the variable
141-
which underlies the accumulator, and returner denotes a function of
142-
one argument, the final value of the accumulator, whose return value
143-
is used instead of the final value. There may in future be additional
144-
keywords.
139+
returner default by). In this case name, operator & initially mean
140+
exactly the same as previously, but type is a type specification for
141+
the variable which underlies the accumulator, and returner denotes a
142+
function of one argument, the final value of the accumulator, whose
143+
return value is used instead of the final value. If default is given
144+
then the local function takes an optional argument whose default it
145+
is. If by is given then it takes no arguments and this is the
146+
increment. These two arguments are mutually exclusive.
145147
146148
The local accumulator functions are declared inline, and return their
147149
argument.
@@ -164,56 +166,77 @@ values, possibly via the returner functions."
164166
(eql (first on) 'lambda)))
165167
(error "the operator of accumulator ~S isn't a symbol or lambda"
166168
a))
167-
`(name ,name on ,on init ,init)))
169+
`(name ,name on ,on init ,init arglist (it))))
168170
(otherwise
169171
(destructuring-bind (name on &key
170172
(initially `(,on))
171-
(type nil)
172-
(returner nil)
173-
(default nil defaultp)) a
173+
(type nil typep)
174+
(returner nil returnerp)
175+
(default nil defaultp)
176+
(by nil byp)) a
174177
(unless (symbolp name)
175178
(error "the name of accumulator ~S isn't a symbol" a))
176179
(unless (or (symbolp on)
177180
(and (consp on)
178181
(eql (first on) 'lambda)))
179182
(error "the operator of accumulator ~S~
180183
isn't a symbol or lambda expression" a))
181-
(unless (or (symbolp returner)
182-
(and (consp returner)
183-
(eql (first returner) 'lambda)))
184-
(error "the return operator of accumulator ~S~
185-
isn't a symbol of lambda expression" a))
186-
`(name ,name on ,on init ,initially
187-
type ,type returner ,returner
188-
arglist ,(if defaultp `(&optional (it ,default)) '(it)))))))
184+
(when returnerp
185+
(unless (or (symbolp returner)
186+
(and (consp returner)
187+
(eql (first returner) 'lambda)))
188+
(error "the return operator of accumulator ~S~
189+
isn't a symbol of lambda expression" a)))
190+
(when (and defaultp byp)
191+
(error "default and by can't both be given in ~S" a))
192+
(let ((p `(name ,name on ,on init ,initially
193+
arglist ,(cond
194+
(byp `(&aux (it ,by)))
195+
(defaultp `(&optional (it ,default)))
196+
(t '(it))))))
197+
(when typep
198+
(setf (getf p 'type) type))
199+
(when returnerp
200+
(setf (getf p 'returner) returner))
201+
p)))))
189202
(t
190203
(error "hopeless accumulator ~S" a))))
191-
(getter (property &optional (default nil))
192-
(lambda (plist)
193-
(getf plist property default))))
204+
(getp (plist p)
205+
(let* ((d (load-time-value (cons nil nil)))
206+
(v (getf plist p d)))
207+
(if (eq v d)
208+
(values nil nil)
209+
(values v t)))))
194210
(let* ((parsed (mapcar #'parse-accumulator accumulators))
195-
(names (mapcar (getter 'name) parsed))
196-
(inits (mapcar (getter 'init) parsed))
197-
(types (mapcar (getter 'type) parsed))
198-
(returners (mapcar (getter 'returner) parsed))
199-
(arglists (mapcar (getter 'arglist) parsed))
200-
(ons (mapcar (getter 'on) parsed))
201-
(vns (mapcar (lambda (name) (make-symbol (symbol-name name)))
202-
names)))
203-
`(let ,(mapcar #'list vns inits)
204-
,@(mapcan (lambda (v tp)
205-
(if tp `((declare (type ,tp ,v))) '()))
206-
vns types)
207-
(flet ,(mapcar (lambda (name on vn arglist)
208-
`(,name ,arglist (setf ,vn (,on ,vn it)) it))
209-
names ons vns arglists)
210-
(declare (inline ,@names))
211+
(vars (mapcar (lambda (p)
212+
(make-symbol (symbol-name (getf p 'name))))
213+
parsed)))
214+
`(let ,(mapcar (lambda (p v)
215+
`(,v ,(getp p 'init)))
216+
parsed vars)
217+
,@(mapcan (lambda (p v)
218+
(multiple-value-bind (type typep) (getp p 'type)
219+
(if typep `((declare (type ,type ,v))) '())))
220+
parsed vars)
221+
(flet ,(mapcar (lambda (p v)
222+
(let ((arglist (getp p 'arglist))
223+
(on (getp p 'on))
224+
(name (getp p 'name)))
225+
`(,name ,arglist (setf ,v (,on ,v it)) it)))
226+
parsed vars)
227+
(declare (inline ,@(mapcar (lambda (p)
228+
(getp p 'name))
229+
parsed)))
211230
,@forms)
212-
(values ,@(mapcar (lambda (vn returner)
213-
(if returner
214-
`(,returner ,vn)
215-
vn))
216-
vns returners))))))
231+
(values ,@(mapcar (lambda (p v)
232+
(multiple-value-bind (returner returnerp) (getp p 'returner)
233+
(if returnerp
234+
`(,returner ,v)
235+
v)))
236+
parsed vars))))))
237+
238+
(with-accumulators ((s + :by 1))
239+
(s))
217240

218241
;;;; Something more like Interlisp-D's DOCOLLECT / ENDCOLLECT / TCONC
219242
;;; See interlisp.org/docs/IRM.pdf

test/test-collecting.lisp

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
(equal '(1 3))
2626
(equal '(2 4))))
2727

28+
(define-test ("org.tfeb.hax.collecting" "accumulators")
29+
(is = 1 (with-accumulators ((a +)) (a 1)))
30+
(is = 1 (with-accumulators ((a + :initially 1))))
31+
(is = 1 (with-accumulators ((a + :returner 1+))))
32+
(is = 1 (with-accumulators ((a + :by 1)) (a)))
33+
(is = 1 (with-accumulators ((a + :default 1)) (a)))
34+
(is = 1 (with-accumulators ((a + :default 1)) (a 1)))
35+
(is = 1 (with-accumulators ((a + :type fixnum)) (a 1))))
36+
2837
(define-test ("org.tfeb.hax.collecting" "collectors")
2938
(let ((c (make-collector)))
3039
(collect-into c 1)

0 commit comments

Comments
 (0)