[heresy-devel] Heresy 0.0.3 (no html) info resend

Matt Lamari matthew.lamari at gmail.com
Mon Mar 31 08:03:14 UTC 2008


http://sourceforge.net/projects/cl-heresy/


----

Structurally, every lazy link, originally a single no-input function, 
has been replaced with a 2-function "link" - one being a zero-input 
function that returns value/next, the other being same but that accepts 
a replacement "end"-link - that is, a value/next generator that is to be 
applied when the end of the original list is struck.  Since the two 
functions are to be near-identical, they are generally created via a 
macro which takes the common form.  The most useful result of this 
feature is that concat/append list actions can be chained up without a 
per-element cost.

For example:

; a is set to a lazy lazy-list of 1, 2, 3, 4
(setf a (map/ #'identity '(1 2 3 4)))
/; (a now traverses to (1 2 3 4))/

; a repeatedly put in a lazy-list, which is provided to concat/ (no 
material change in result; but lazy-evaluation
; stores up each step)
(loop for i from 1 to 1000000 do (setf a (concat/ (map/ #'identity (list 
a)))))
/; (a still traverses to (1 2 3 4); but through 1000000 concat/ blocks)/

Traversal of /a/ now bears a single cost - once to obtain the first 
value, once to discover the end.  That is, the cost is borne when 
"switching depths" of concatenated lists only.  There is no per-element 
cost for the 4 numbers, nor does the traversal consume the stack.

----

Every call has the choice to return a result of type '/unresolved/ - 
which can be created by the macro /unresolved/.  An /unresolved/ 
contains a zero-parameter lambda/function that returns the true result, 
with the type being used to switch on.

The rationale for this mechanism is to prevent the problem of functions 
calling functions calling functions, and consuming the stack.  The macro 
/resolved/ repeatedly evaluates an /unresolved/ until the result is no 
longer an /unresolved/.  This is leveraged throughout the library in 
order to allow a form to return the result of a call; but being able to 
subtract its own stack depth from the equation.

Here is a simple example of A calling B calling C


(defun c ()
  (unresolved (+ 3 4))) ; returns unresolved 3 + 4

(defun b ()
  (unresolved (c)))  ; returns unresolved call to c

(defun a ()
  (unresolved (b))) ; returns unresolved call to b


(print (resolved (a))) ; the /resolved/ macro takes an /unresolved/ from 
the call to A, and repeatedly resolves to the final result 7.
        ; /At no point /do successive calls to the 3 functions appear on 
the same stack.

The /resolved/ and /unresolved /macros in heresy.lisp may explain this 
better than I can in words.

The symbols for /resolved/ and /unresolved/ are not currently exported 
by Heresy, nor are they documented - they are currently an 
unexposed/undocumented internal tool for chaining up effects expressed 
via successive function-calls without stack usage.  This will likely 
change in a future revision, as there are instances where the user of 
the library may wish to express a lazy-list or map function in terms of 
/unresolved/, yet still leverage compatibility through Heresy functions 
such as /filter// resolving the values on an as-needed basis.  (This can 
be leveraged now through the unexported symbols).

----

A battery of unit-tests have been added, to attempt to gain 
code-coverage of lazy-functions that have different code paths depending 
upon the type/characteristics of the input list, and tested in both lazy 
and eager contexts.  Functions that return lazy-lists have said lists 
tested both standalone and concatenated, so as to ensure that the 
distinct code-paths for each use are tested.  This results in 4 x 2 x 2 
combinations for each list-returning test.  The complexity of this new 
structure (number of potential failure combinations) mandate test-driven 
development going forward.





More information about the heresy-devel mailing list