[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