Permalink
Browse files

Add files to the repository

  • Loading branch information...
0 parents commit a27785dbfbca71a51b48569996acc7e0818bc02d @tlikonen committed Jul 18, 2011
Showing with 552 additions and 0 deletions.
  1. +226 −0 README.org
  2. +321 −0 accumulator.lisp
  3. +5 −0 general-accumulator.asd
@@ -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.

0 comments on commit a27785d

Please sign in to comment.