Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a27785d
Showing
3 changed files
with
552 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
* General accumulator | ||
|
||
General accumulator is a general-purpose, extensible value accumulator | ||
library for Common Lisp language. Its main interface is | ||
=with-accumulator= macro which sets an environment for easy | ||
accumulation. The library provides several built-in accumulators which | ||
should cover the common use-cases but any kind of accumulators can be | ||
added because the accumulator back-end is implemented through generic | ||
functions. | ||
|
||
** The interface | ||
|
||
#+BEGIN_SRC lisp | ||
(with-accumulator (NAME OBJECT &key KEYWORD-ARGUMENTS ...) | ||
BODY ...) | ||
#+END_SRC | ||
|
||
The =with-accumulator= macro creates an accumulation environment in | ||
which a local function /name/ handles the accumulation. Accumulator's | ||
type is defined by the /object/ argument. Then all /body/ forms are | ||
executed normally and the return value of the last form is returned. | ||
|
||
The local function /name/ can optionally take one argument which is an | ||
object to be accumulated. If the function is called without arguments it | ||
returns the currently accumulated value. | ||
|
||
The accumulation process is handled by generic functions =initialize=, | ||
=accumulate= and =value=. | ||
|
||
** Built-in accumulators | ||
|
||
There are several built-in accumulator types. Possible values for the | ||
/object/ argument of =with-accumulator= macro: | ||
|
||
*** =:LIST= | ||
|
||
Creates a list collector. Each accumulated object is collected to a | ||
list. Example: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (collect :list) | ||
(collect 1) (collect 2) (collect 3) | ||
(collect)) | ||
(1 2 3) | ||
#+END_SRC | ||
|
||
The collecting is done destructively. The applicable /accumulate/ method | ||
maintains a pointer to the last cons cell of the list and each time | ||
modifies its /cdr/ value to point to a new cons cell. | ||
|
||
*** A list | ||
|
||
If the /object/ argument is of type /list/ then new elements are | ||
collected at the end. Example: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (collect (list 1 2 3)) | ||
(collect 4) (collect 5) | ||
(collect)) | ||
(1 2 3 4 5) | ||
#+END_SRC | ||
|
||
This is a destructive operation. The /cdr/ value of the last cons cell | ||
of the original list is modified and linked to a new cons cell. | ||
|
||
*** =:VECTOR= | ||
|
||
Creates a general vector collector. It creates an adjustable vector with | ||
a fill pointer 0 and element type /t/. New elements are pushed to that | ||
vector with =cl:vector-push-extend= function. Example: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (collect :vector) | ||
(collect "first") (collect "second") | ||
(collect)) | ||
#("first" "second") | ||
#+END_SRC | ||
|
||
*** =:STRING= | ||
|
||
This is similar to =:VECTOR= but the element type is /character/. The | ||
underlying /accumulate/ methods can take a single /character/ or a | ||
sequence of characters as the argument. Example: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (collect :string) | ||
(collect #\a) | ||
(collect "bcd") | ||
(collect #(#\e #\f)) | ||
(collect '(#\g #\h #\i)) | ||
(collect)) | ||
"abcdefghi" | ||
#+END_SRC | ||
|
||
*** =:BIT-VECTOR= | ||
|
||
This is similar to =:STRING= but the element type is /bit/. The argument | ||
for the accumulator function can a /bit/ or a sequence of bits. | ||
|
||
*** A vector | ||
|
||
If /object/ is of type /vector/ which satisfies the test | ||
=array-has-fill-pointer-p= then that vector is appended, starting from | ||
its current fill pointer. | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator | ||
(collect (make-array 2 :fill-pointer 2 :adjustable t | ||
:initial-contents (vector 1 2))) | ||
(collect 3) | ||
(collect 4) | ||
(collect)) | ||
#(1 2 3 4) | ||
#+END_SRC | ||
|
||
Note that if the vector is not adjustable then the accumulator may reach | ||
vector's limits and =cl:vector-push-extend= signals an error. | ||
|
||
*** A function | ||
|
||
If /object/ is of type /function/ then the accumulator behaves like the | ||
=cl:reduce= function: all accumulated objects are combined into one by | ||
calling the given reducer function. Examples: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (summing #'+) | ||
(summing 5) (summing 7) (summing 11) | ||
(summing)) | ||
23 | ||
|
||
GENACC> (with-accumulator (nc #'nconc) | ||
(nc (list 1 2 3)) | ||
(nc (list 4 5 6)) | ||
(nc (list 7 8 9)) | ||
(nc)) | ||
(1 2 3 4 5 6 7 8 9) | ||
|
||
GENACC> (with-accumulator (early-char (lambda (a b) | ||
(if (char< a b) a b))) | ||
(early-char #\o) | ||
(early-char #\b) | ||
(early-char #\s) | ||
(early-char)) | ||
#\b | ||
#+END_SRC | ||
|
||
Note that a single reduce operation for a sequence is probably faster | ||
with just =cl:reduce= function than with =with-accumulator= macro. | ||
Therefore, the macro could be used only for collecting values into a | ||
sequence and =cl:reduce= would be used for the actual reduce operation. | ||
On the other hand, an advantage of doing all the reducing work with | ||
=with-accumulator= is that the macro does it one step at the time. | ||
Intermediate results of the reducing are always available. | ||
|
||
** Adding a custom accumulator | ||
|
||
The whole accumulation process is handled by three generic functions: | ||
=initialize=, =accumulate= and =value=. Writing new methods for those | ||
functions allow adding any kind of accumulators. The following example | ||
adds an accumulator which calculates the arithmetic mean of accumulated | ||
numbers. | ||
|
||
First we define a class whose instances will keep the state of the | ||
accumulator. In this case we need to store the sum and the count of | ||
accumulated numbers so we create slots for them. | ||
|
||
#+BEGIN_SRC lisp | ||
(defclass mean-accumulator () | ||
((sum :initform 0) | ||
(count :initform 0))) | ||
#+END_SRC | ||
|
||
Then we add a method for initializing an instance of the class. The | ||
generic function =initialize= is used for that. It is called with the | ||
/object/ argument of =with-accumulator= macro and with optional | ||
/keyword-arguments/. In this example we use an /EQL/ specializer for | ||
symbol =:MEAN=. We don't use any keyword arguments so there's just empty | ||
/&key/ at the end of the lambda list. | ||
|
||
#+BEGIN_SRC lisp | ||
(defmethod genacc:initialize ((type (eql :mean)) &key) | ||
(make-instance 'mean-accumulator)) | ||
#+END_SRC | ||
|
||
Now we create a method for generic function =accumulate=. The function | ||
is called with two arguments: | ||
|
||
1. the accumulator object created by =initialize= | ||
2. the object that is meant to be accumulated. | ||
|
||
This method specializes on our =mean-accumulator= class as well as on | ||
/number/ class. The number is added to the previous value and the count | ||
is increased by one. | ||
|
||
#+BEGIN_SRC lisp | ||
(defmethod genacc:accumulate ((object mean-accumulator) | ||
(number number)) | ||
(with-slots (sum count) object | ||
(incf sum number) | ||
(incf count 1))) | ||
#+END_SRC | ||
|
||
For returning the accumulated mean value we create a method for the | ||
generic function =value=. This method, too, must specialize on the | ||
=mean-accumulator= class. We get the current accumulated mean value by | ||
dividing the value of /sum/ slot with the value of /count/ slot. | ||
|
||
#+BEGIN_SRC lisp | ||
(defmethod genacc:value ((object mean-accumulator)) | ||
(with-slots (sum count) object | ||
(/ sum count))) | ||
#+END_SRC | ||
|
||
Now the custom accumulator is ready and it can be used with the | ||
=with-accumulator= macro. Example: | ||
|
||
#+BEGIN_SRC lisp | ||
GENACC> (with-accumulator (mean :mean) | ||
(loop repeat 10 do (mean (random 1000))) | ||
(format t "The mean so far: ~A~%" (mean)) | ||
(loop repeat 10 do (mean (random 1000))) | ||
(format t "The final mean: ~A~%" (mean))) | ||
The mean so far: 2512/5 | ||
The final mean: 2704/5 | ||
NIL | ||
#+END_SRC |
Oops, something went wrong.