API Reference


A library for working with financial amounts involving multiple commodities.


  • Function MAKE-COMMODITY-POOL (&key ((by-name-map by-name-map) (make-hash-table :test 'equal)) ((default-commodity default-commodity) nil))
  • Variable *EXTRA-PRECISION*
  • Function ANNOTATION-PRICE (instance)
  • Function (setf ANNOTATION-PRICE) (value instance)
  • Function ANNOTATION-DATE (instance)
  • Function (setf ANNOTATION-DATE) (value instance)
  • Function ANNOTATION-TAG (instance)
  • Function (setf ANNOTATION-TAG) (value instance)
  • Function MAKE-COMMODITY-ANNOTATION (&key ((price price) nil) ((date date) nil) ((tag tag) nil))
  • Function ANNOTATED-COMMODITY-P (object)
  • Struct AMOUNT
  • Function AMOUNT-P (object)
  • Function AMOUNT-COMMODITY (instance)
  • Function (setf AMOUNT-COMMODITY) (value instance)
  • Function AMOUNT-QUANTITY (instance)
  • Function (setf AMOUNT-QUANTITY) (value instance)
  • Function AMOUNT-KEEP-PRECISION-P (instance)
  • Function (setf AMOUNT-KEEP-PRECISION-P) (value instance)
  • Function BALANCE-P (object)
  • Function GET-AMOUNTS-MAP (instance)
  • Function (setf GET-AMOUNTS-MAP) (value instance)
  • Type VALUE
  • Function VALUEP (object)
  • Generic-Function VALUE-ZEROP (value)
  • Generic-Function VALUE-ZEROP* (value)
  • Generic-Function VALUE-MINUSP (value)
  • Generic-Function VALUE-MINUSP* (value)
  • Generic-Function VALUE-PLUSP (value)
  • Generic-Function VALUE-PLUSP* (value)
  • Generic-Function COMPARE (left right)
  • Generic-Function COMPARE* (left right)
  • Generic-Function VALUE-EQUAL (left right)
  • Generic-Function VALUE-EQUALP (left right)
  • Generic-Function VALUE-NOT-EQUAL (left right)
  • Generic-Function VALUE-NOT-EQUALP (left right)
  • Generic-Function VALUE= (left right)
  • Generic-Function VALUE/= (left right)
  • Generic-Function VALUE-ABS (value)
  • Generic-Function NEGATE (value)
  • Generic-Function ADD (value-a value-b)
  • Generic-Function SUBTRACT (value-a value-b)
  • Generic-Function MULTIPLY (value-a value-b)
  • Generic-Function DIVIDE (value-a value-b)
  • Generic-Function PRINT-VALUE (value &key output-stream omit-commodity-p full-precision-p width latter-width line-feed-string)
  • Generic-Function FORMAT-VALUE (value &key omit-commodity-p full-precision-p width latter-width line-feed-string)
  • Generic-Function COMMODITY-NAME (item &optional no-annotation)
  • Generic-Function DISPLAY-PRECISION (item)
  • Generic-Function AMOUNT-PRECISION (item)
  • Generic-Function MARKET-VALUE (any-item &optional fixed-time)
  • Generic-Function COMMODITY-ANNOTATION (item)
  • Generic-Function COMMODITY-ANNOTATION-EQUAL (left-item right-item)
  • Generic-Function ANNOTATE-COMMODITY (item annotation)
  • Generic-Function STRIP-ANNOTATIONS (any-item &key keep-price keep-date keep-tag)
  • Function COMMODITY-BUILTIN-P (comm)
  • Function COMMODITY-POOL (comm)
  • Condition AMOUNT-ERROR  (ERROR)
  • Method AMOUNT-PRECISION ((item rational))
  • Method AMOUNT-PRECISION ((item amount))
  • Function AMOUNT (string &key (pool *default-commodity-pool*))
  • Function AMOUNT* (string &key (pool *default-commodity-pool*))
  • Function PARSE-AMOUNT (&rest args)
  • Function PARSE-AMOUNT* (&rest args)
  • Function EXACT-AMOUNT (string &key (pool *default-commodity-pool*))
  • Function VALUE-MAYBE-ROUND (amount)
  • Method VALUE-ZEROP ((rational rational))
  • Method VALUE-ZEROP ((amount amount))
  • Method VALUE-ZEROP* ((rational rational))
  • Method VALUE-ZEROP* ((amount amount))
  • Method VALUE-MINUSP ((rational rational))
  • Method VALUE-MINUSP ((amount amount))
  • Method VALUE-MINUSP* ((rational rational))
  • Method VALUE-MINUSP* ((amount amount))
  • Method VALUE-PLUSP ((rational rational))
  • Method VALUE-PLUSP ((amount amount))
  • Method VALUE-PLUSP* ((rational rational))
  • Method VALUE-PLUSP* ((amount amount))
  • Function COMMODITY-EQUAL (a b)
    Two commodities are EQUAL if they are the same object.
  • Function COMMODITY-EQUALP (a b)
  • Method VALUE-EQUAL ((left rational) (right rational))
  • Method VALUE-EQUAL ((left rational) (right amount))
  • Method VALUE-EQUAL ((left rational) (right balance))
  • Method VALUE-EQUAL ((left amount) (right rational))
  • Method VALUE-EQUAL ((left amount) (right amount))
  • Method VALUE-EQUAL ((left amount) (right balance))
  • Method VALUE-EQUAL ((left balance) (right rational))
  • Method VALUE-EQUAL ((left balance) (right amount))
  • Method VALUE-EQUAL ((left balance) (right balance))
  • Method VALUE-EQUALP ((left rational) (right rational))
  • Method VALUE-EQUALP ((left rational) (right amount))
  • Method VALUE-EQUALP ((left rational) (right balance))
  • Method VALUE-EQUALP ((left amount) (right rational))
  • Method VALUE-EQUALP ((left amount) (right amount))
  • Method VALUE-EQUALP ((left amount) (right balance))
  • Method VALUE-EQUALP ((left balance) (right rational))
  • Method VALUE-EQUALP ((left balance) (right amount))
  • Method VALUE-EQUALP ((left balance) (right balance))
  • Method VALUE-NOT-EQUAL ((left rational) (right rational))
  • Method VALUE-NOT-EQUAL ((left rational) (right amount))
  • Method VALUE-NOT-EQUAL ((left rational) (right balance))
  • Method VALUE-NOT-EQUAL ((left amount) (right rational))
  • Method VALUE-NOT-EQUAL ((left amount) (right amount))
  • Method VALUE-NOT-EQUAL ((left amount) (right balance))
  • Method VALUE-NOT-EQUAL ((left balance) (right rational))
  • Method VALUE-NOT-EQUAL ((left balance) (right amount))
  • Method VALUE-NOT-EQUAL ((left balance) (right balance))
  • Method VALUE-NOT-EQUALP ((left rational) (right rational))
  • Method VALUE-NOT-EQUALP ((left rational) (right amount))
  • Method VALUE-NOT-EQUALP ((left rational) (right balance))
  • Method VALUE-NOT-EQUALP ((left amount) (right rational))
  • Method VALUE-NOT-EQUALP ((left amount) (right amount))
  • Method VALUE-NOT-EQUALP ((left amount) (right balance))
  • Method VALUE-NOT-EQUALP ((left balance) (right rational))
  • Method VALUE-NOT-EQUALP ((left balance) (right amount))
  • Method VALUE-NOT-EQUALP ((left balance) (right balance))
  • Method VALUE= ((left rational) (right rational))
  • Method VALUE= ((left rational) (right amount))
  • Method VALUE= ((left rational) (right balance))
  • Method VALUE= ((left amount) (right rational))
  • Method VALUE= ((left amount) (right amount))
  • Method VALUE= ((left amount) (right balance))
  • Method VALUE= ((left balance) (right rational))
  • Method VALUE= ((left balance) (right amount))
  • Method VALUE= ((left balance) (right balance))
  • Method VALUE/= ((left rational) (right rational))
  • Method VALUE/= ((left rational) (right amount))
  • Method VALUE/= ((left rational) (right balance))
  • Method VALUE/= ((left amount) (right rational))
  • Method VALUE/= ((left amount) (right amount))
  • Method VALUE/= ((left amount) (right balance))
  • Method VALUE/= ((left balance) (right rational))
  • Method VALUE/= ((left balance) (right amount))
  • Method VALUE/= ((left balance) (right balance))
  • Function VALUE-LESSP (left right)
  • Function VALUE-LESSP* (left right)
  • Function VALUE-LESSEQP (left right)
  • Function VALUE-LESSEQP* (left right)
  • Function VALUE< (left right)
  • Function VALUE<= (left right)
  • Function VALUE-GREATERP (left right)
  • Function VALUE-GREATERP* (left right)
  • Function VALUE-GREATEREQP (left right)
  • Function VALUE-GREATEREQP* (left right)
  • Function VALUE> (left right)
  • Function VALUE>= (left right)
  • Method VALUE-ABS ((rational rational))
  • Method VALUE-ABS ((amount amount))
  • Method VALUE-ABS ((balance balance))
  • Function VALUE-ROUND (amount &optional precision)
    Round the given AMOUNT to the stated PRECISION. If PRECISION is less than the current internal precision, data will be lost. If it is greater, this operation has no effect.
  • Method NEGATE ((rational rational))
  • Method NEGATE ((amount amount))
  • Method NEGATE ((balance balance))
  • Function AMOUNT-IN-BALANCE (balance commodity)
  • Method ADD ((left rational) (right rational))
  • Method ADD ((left rational) (right amount))
  • Method ADD ((left rational) (right balance))
  • Method ADD ((left amount) (right rational))
  • Method ADD ((left amount) (right amount))
  • Method ADD ((left amount) (right balance))
  • Method COMPARE ((left rational) (right rational))
  • Method COMPARE ((left amount) (right rational))
  • Method COMPARE ((left rational) (right amount))
  • Method COMPARE ((left amount) (right amount))
  • Method COMPARE* ((left rational) (right rational))
  • Method COMPARE* ((left amount) (right rational))
  • Method COMPARE* ((left rational) (right amount))
  • Method COMPARE* ((left amount) (right amount))
  • Function SIGN (amount)
    Return -1, 0 or 1 depending on the sign of AMOUNT.
  • Function SIGN* (amount)
    Return -1, 0 or 1 depending on the sign of AMOUNT.
  • Function FIND-ANNOTATED-COMMODITY (name-or-commodity details &key (create-if-not-exists-p nil) (pool *default-commodity-pool*))
    Find an annotated commodity matching the commodity symbol NAME, which may be a STRING or a COMMODITY-SYMBOL, and given set of commodity DETAILS, of type COMMODITY-ANNOTATION. Returns two values: COMMODITY or NIL, NEWLY-CREATED-P
  • Function READ-AMOUNT (in &key (observe-properties-p t) (pool *default-commodity-pool*))
    Parse an AMOUNT from the input stream IN. If :OBSERVE-PROPERTIES-P is T (the default), any display details noticed in this amount will be set as defaults for displaying this kind of commodity in the future. If :POOL is set, any commodities created by this routine (a maximum possible of two, if an annotated price is given with a second commodity) will be associated with the given commodity pool. The possible syntax for an amount is: [-]NUM[ ]SYM [ANNOTATION] SYM[ ][-]NUM [ANNOTATION]
  • Function READ-AMOUNT* (in &key (pool *default-commodity-pool*))
  • Function READ-EXACT-AMOUNT (in &key (pool *default-commodity-pool*))
  • Function BALANCE-COMMODITIES (balance)
  • Function BALANCE-AMOUNTS (balance)
  • Function BALANCE-FIRST-AMOUNT (balance)
  • Function BALANCE-COMMODITY-COUNT (balance)
  • Function COMMODITY-LESSP (left right)
    Return T if commodity LEFT should be sorted before RIGHT.
  • Method ADD ((left balance) (right rational))
  • Method ADD ((left balance) (right amount))
  • Method ADD ((left balance) (right balance))
  • Method SUBTRACT ((left rational) (right rational))
  • Method SUBTRACT ((left rational) (right amount))
  • Method SUBTRACT ((left rational) (right balance))
  • Method SUBTRACT ((left amount) (right rational))
  • Method SUBTRACT ((left amount) (right amount))
  • Method SUBTRACT ((left amount) (right balance))
  • Method SUBTRACT ((left balance) (right rational))
  • Method SUBTRACT ((left balance) (right amount))
  • Method SUBTRACT ((left balance) (right balance))
  • Method MULTIPLY ((left rational) (right rational))
  • Method MULTIPLY ((left rational) (right amount))
  • Method MULTIPLY ((left rational) (right balance))
  • Method MULTIPLY ((left amount) (right rational))
  • Method MULTIPLY ((left amount) (right amount))
  • Method MULTIPLY ((left balance) (right rational))
  • Method MULTIPLY ((left balance) (right amount))
  • Method DIVIDE ((left rational) (right rational))
  • Method DIVIDE ((left rational) (right amount))
  • Method DIVIDE ((left rational) (right balance))
  • Method DIVIDE ((left amount) (right rational))
  • Method DIVIDE ((left amount) (right amount))
  • Method DIVIDE ((left balance) (right rational))
  • Method DIVIDE ((left balance) (right amount))
  • Method PRINT-VALUE ((integer integer) &key (output-stream *standard-output*) omit-commodity-p full-precision-p (width nil) latter-width line-feed-string)
  • Method PRINT-VALUE ((rational rational) &key (output-stream *standard-output*) omit-commodity-p full-precision-p (width nil) latter-width line-feed-string)
  • Method PRINT-VALUE ((amount amount) &key (output-stream *standard-output*) (omit-commodity-p nil) (full-precision-p nil) (width nil) latter-width line-feed-string)
  • Function COMPARE-AMOUNTS-VISUALLY (left right)
  • Method PRINT-VALUE ((balance balance) &key (output-stream *standard-output*) (omit-commodity-p nil) (full-precision-p nil) (width 12) (latter-width nil) (line-feed-string (format nil "~c" #\newline)))
  • Method FORMAT-VALUE ((rational rational) &key omit-commodity-p full-precision-p (width nil) latter-width line-feed-string)
  • Method FORMAT-VALUE ((amount amount) &key (omit-commodity-p nil) (full-precision-p nil) (width nil) latter-width line-feed-string)
  • Method FORMAT-VALUE ((balance balance) &key (omit-commodity-p nil) (full-precision-p nil) (width nil) (latter-width nil) (line-feed-string (format nil "~c" #\newline)))
  • Method COMMODITY-NAME ((null null) &optional no-annotation)
  • Method COMMODITY-NAME ((commodity commodity) &optional no-annotation)
  • Method COMMODITY-NAME ((amount amount) &optional no-annotation)
  • Method DISPLAY-PRECISION ((commodity commodity))
  • Method DISPLAY-PRECISION ((annotated-commodity annotated-commodity))
  • Method DISPLAY-PRECISION ((amount amount))
  • Method MARKET-VALUE ((commodity commodity) &optional fixed-time)
  • Method MARKET-VALUE ((annotated-commodity annotated-commodity) &optional fixed-time)
  • Method MARKET-VALUE ((rational rational) &optional fixed-time)
  • Method MARKET-VALUE ((amount amount) &optional fixed-time)
  • Method MARKET-VALUE ((balance balance) &optional fixed-time)
  • Function EXCHANGE-COMMODITY (amount &key (total-cost nil) (per-unit-cost nil) (moment nil) (tag nil))
  • Function RESET-COMMODITY-POOL (&optional pool)
  • Macro PUSHEND (item the-list &optional final-cons)
  • Function FIND-COMMODITY (name &key (create-if-not-exists-p nil) (pool *default-commodity-pool*))
    Find a COMMODITY identifier by the symbol name found by parsing NAME. The NAME can be either a string or an input stream, or nil, in which case the name is read from *STANDARD-INPUT*. The argument :POOL specifies the commodity pool which will maintain this commodity, and by which other code may access it again. The argument :CREATE-IF-NOT-EXISTS-P indicates whether a new commodity should be created if one cannot already be found. The return values are: COMMODITY or NIL, NEWLY-CREATED-P
  • Method ANNOTATE-COMMODITY ((commodity commodity) (details commodity-annotation))
  • Method ANNOTATE-COMMODITY ((amount amount) (details commodity-annotation))
  • Method COMMODITY-ANNOTATION ((commodity commodity))
  • Method COMMODITY-ANNOTATION ((annotated-commodity annotated-commodity))
  • Method COMMODITY-ANNOTATION ((amount amount))
  • Method COMMODITY-ANNOTATION-EQUAL ((a commodity-annotation) (b commodity-annotation))
  • Method STRIP-ANNOTATIONS ((annotated-commodity annotated-commodity) &key keep-price keep-date keep-tag)
  • Method STRIP-ANNOTATIONS ((amount amount) &key keep-price keep-date keep-tag)
  • Method STRIP-ANNOTATIONS ((balance balance) &key keep-price keep-date keep-tag)


Test system for CAMBL.


    test-case for CAMBL commodities
    No slots.
    test-case for CAMBL amounts
    No slots.
  • Function RUN-TESTS


Functional programming utilities: iteration over immutable lists sharing identical sublists.


  • Macro APPLY-TO-LIST (list predicate function &key (first-only nil) (skip-to-next t) (lookahead t))
    Given an input LIST, replicate as much of its structure as possible while applying some kind of transform on its value. For every member of the list where PREDICATE returns T, FUNCTION is called with the list whose CAR is that member; FUNCTION should return the list which will be substituted at that point (this makes it possible to remove, change or insert the matched cell). If FIRST-ONLY is NIL, this is only done for every cell that matches. Note: Be very careful when setting FIRST-ONLY to T, for if FUNCTION returns a new list that also matches PREDICATE, an infinitely recursive loop can occur. If SKIP-TO-NEXT is T, scanning the function contains with the CDR of the value returned by FUNCTION, which can never lead to infinite recursion. If LOOKAHEAD is T, the list is prescanned to see if PREDICATE matches, otherwise copying is done regardless of whether there is a match in LIST or not; this mimics the behavior of CL:MAPCAR and is better for very short lists. This function depends on the following contract with the caller: 1. The input LIST is immutable after any call to APPLY-TO-LIST until the end of the program. 2. The returned LIST is likewise immutable. The memory savings offered by this function comes at two costs: The first is the subsequent immutability of the input data, and the second is an increase in functional complexity. Specifically, while CL:MAPCAR is O(N) for a given list, FPROG:APPLY-TO-LIST -- when used to implement a sharing form of MAPCAR, such as FPROG:MAPCAR-IF -- has complexity O(N) in the best case, and O(2N) in the worst case when LOOKAHEAD is T, otherwise it is also O(N) (where an element to be substituted occurs at the very end of the list). Now, the cost of speed in the worst case can lead to dramatic improvements in memory usage in the average case, with an attendant speed advantage. Take the case of a list which is 500 elements long. In my environment, here are the timings for using MAPCAR to generate a new list from an old one where only one cons cell needs to be changed. These times were determined by calling the same code repeatedly 1,000,000 times (that code is near the end of this file, in the function TIMING-TESTS): Evaluation took: 8.367 seconds of real time 7.931782 seconds of user run time 0.342331 seconds of system run time [Run times include 2.679 seconds GC run time.] 0 calls to %EVAL 0 page faults and 4,024,029,056 bytes consed. That's 4 gigabytes of memory, probably to be expected. The only reason this doesn't blow the heap is because all of the intermediate results are being thrown away, making a lot of the cons'ing "free". If the results are kept, the MAPCAR solution becomes impossible without dramatically increasing Lisp's heap size. The memory and time costs of using MAPCAR in this example are constant no matter whether the cons cell is substituted at the beginning, middle or end of the 500 element list. To compare, here are the time and memory statistics from FPROG:MAPCAR-IF for the same data, in all three cases (best, average, worst): Evaluation took: 3.478 seconds of real time 3.474324 seconds of user run time 0.003887 seconds of system run time [Run times include 0.026 seconds GC run time.] 0 calls to %EVAL 0 page faults and 40,007,952 bytes consed. In the best case, memory usage is reduced by two orders of magnitude, with an appreciable boost in speed. If the results of this case are saved (using COLLECT in the LOOP instead of DO), the speed savings can become dramatic. Note also that except for the immutability constraints, the results from the two different approaches are EQUAL. Evaluation took: 7.495 seconds of real time 7.272269 seconds of user run time 0.173947 seconds of system run time [Run times include 1.416 seconds GC run time.] 0 calls to %EVAL 0 page faults and 2,032,015,008 bytes consed. In the average case (middle of the list), memory usage is cut in half, while runtime speed is still faster. The cons'ing of CL:MAPCAR also gets more expensive the more the results are kept, so this trivial speed tests -- where no results are saved -- is not exactly fair between the two. But even still FPROG:MAPCAR-IF is doing well. Evaluation took: 11.343 seconds of real time 10.969349 seconds of user run time 0.327477 seconds of system run time [Run times include 2.679 seconds GC run time.] 0 calls to %EVAL 0 page faults and 4,024,030,568 bytes consed. Finally, the pathological case, where MAPCAR-IF degenerates into an exact duplicate of MAPCAR. Memory use is the same, but speed is much slower because the call to MEMBER-IF is searching the entire list before we decide that all of it needs duplication. Below are possible FUNCTION arguments relating to the three canonical uses of APPLY-TO-LIST, plus the names of provided convenience functions which offer simple implementations of them: 1. Modify an existing element: #'(lambda (list) (cons new-cons (cdr list))) Use: (FPROG:MAPCAR-IF predicate function list) 2. Remove an existing element: #'(lambda (list) (cdr list)) Use: (FPROG:REMOVE-IF predicate list) 3. Add a new element: #'(lambda (list) (cons new-cons list)) Use: (FPROG:INSERT-IF predicate function list) Note: When removing elements, SKIP-TO-NEXT must be set to NIL, otherwise further matching elements might be missed. For modifying and adding elements, SKIP-TO-NEXT is likely to always be T (the default). The functionality offered by APPLY-TO-LIST is that every cons cell from the original LIST, after the last matching member, is shared entirely. This is quite different from COPY-LIST, which creates new cons cells for every position -- even those that do not require a unique structure. For example, consider the following list: (defparameter *alist* '((a . 1) (b . 2) (e . 3) (f . 6) (g . 7))) The idea is to return another version of this immutable list, while sharing as much structure as possible -- because the return value is also considered immutable. The following function call achieves this, using the Modify pattern from above: (apply-to-list *alist* #'(lambda (member) (eq 'e (car member))) #'(lambda (list) (cons (cons (caar list) 5) (cdr list)))) => '((a . 1) (b . 2) (e . 5) (f . 6) (g . 7)) In the returned list, 15 atoms are shared with the original, while one new cons cell and one new atom are created: 1, 2, 3: (a . 1) 4, 5, 6: (b . 2) 7: e 8, 9, 10 11: ((f . 6) ...) 12, 13, 14, 15: ((g . 7)) The usual practice of calling MAPCAR and changing the incorrect element would have result in sharing only 13 atoms. That code might have looked like this: (mapcar #'(lambda (cell) (if (eq 'e (car cell)) (cons (car cell) 5) cell)) *alist*) Further, while a discrepancy of 2 cons cells may not seem like much in this example, the difference increases by one for every cell beyond the cell that matches. Thus, if the input list had contained 100 cells beyond (e . 3), the difference would have been 102 cells, and not merely 2. Finally, in our example exactly 4 new cons cells and 1 new atom were created as a result of the call: 1: ((a . 1) ...) 2: ((b . 2) ...) 3: ((e . 5) ...) 4: (e . 5) 5: 5 This is the minimum amount of new information required to represent a new structure where the only change is that 'e' is paired with 5 instead of 3. The idea of APPLY-TO-LIST is to support efficient functional programming, whereat immutable outputs are derived from immutable inputs by efficiently sharing as much structure as possible -- resulting in the least new memory allocated. In cases where no references are held, this offers little gain over advanced generational garbage collection (such as lists passed within a recursive function); but where the results are held over the longer term, such as a series of computed values stored in a results list, the savings of this function can become substantial. It was exactly this kind of sitation that motivated APPLY-TO-LIST: it made it possible to reduce overall memory consumption by a factor of 20, without introducing any additional complexity into the calling code.
  • Function MAPCAR-IF (predicate function list)
  • Function REMOVE-IF (predicate list)
  • Function INSERT-IF (predicate function list)