~ chicken-core (chicken-5) 58bc37692fff883dd735ec320087885aca5da603
commit 58bc37692fff883dd735ec320087885aca5da603 Author: Evan Hanson <evhan@foldling.org> AuthorDate: Wed Oct 30 20:43:10 2013 +1300 Commit: Peter Bex <peter.bex@xs4all.nl> CommitDate: Sun Nov 3 12:33:41 2013 +0100 R7RS delay/force/delay-force Implement R7RS's lazy semantics, specifically the space-safe tail-recursive forcing via delay-force, and a set of tests (mostly taken from SRFI 45). Also make promise a single-slot record type, whose value is dispatched on by type when forcing (since it can now be a procedure, the resulting values, or (in the case of iterative forcing) another promise). Signed-off-by: Peter Bex <peter.bex@xs4all.nl> diff --git a/distribution/manifest b/distribution/manifest index c16826ed..32da5357 100644 --- a/distribution/manifest +++ b/distribution/manifest @@ -121,6 +121,7 @@ tests/runbench.sh tests/srfi-4-tests.scm tests/srfi-13-tests.scm tests/srfi-14-tests.scm +tests/srfi-45-tests.scm tests/simple-thread-test.scm tests/mutex-test.scm tests/hash-table-tests.scm diff --git a/expand.scm b/expand.scm index 2f34df36..5838bcf4 100644 --- a/expand.scm +++ b/expand.scm @@ -1316,6 +1316,16 @@ (##sys#er-transformer (lambda (form r c) (##sys#check-syntax 'delay form '(_ _)) + `(,(r 'delay-force) + (##sys#make-promise + (##sys#call-with-values (##core#lambda () ,(cadr form)) ##sys#list)))))) + +(##sys#extend-macro-environment + 'delay-force + '() + (##sys#er-transformer + (lambda (form r c) + (##sys#check-syntax 'delay-force form '(_ _)) `(##sys#make-promise (##core#lambda () ,(cadr form)))))) (##sys#extend-macro-environment diff --git a/library.scm b/library.scm index d95724ac..364b4778 100644 --- a/library.scm +++ b/library.scm @@ -334,17 +334,29 @@ EOF (##core#inline "C_i_check_closure_2" x (car loc)) (##core#inline "C_i_check_closure" x) ) ) -(define (##sys#force promise) - (if (##sys#structure? promise 'promise) - (apply ##sys#values - (or (##sys#slot promise 2) - (let ((results (##sys#call-with-values (##sys#slot promise 1) (lambda xs xs)))) - (or (##sys#slot promise 2) - (begin - (##sys#setslot promise 1 #f) - (##sys#setslot promise 2 results) - results))))) - promise)) +(define (##sys#force obj) + (if (##sys#structure? obj 'promise) + (let lp ((promise obj) + (forward #f)) + (let ((val (##sys#slot promise 1))) + (cond ((null? val) (##sys#values)) + ((pair? val) (apply ##sys#values val)) + ((procedure? val) + (when forward (##sys#setslot forward 1 promise)) + (let ((results (##sys#call-with-values val ##sys#list))) + (cond ((not (procedure? (##sys#slot promise 1))) + (lp promise forward)) ; in case of reentrance + ((and (not (null? results)) (null? (cdr results)) + (##sys#structure? (##sys#slot results 0) 'promise)) + (let ((result0 (##sys#slot results 0))) + (##sys#setslot promise 1 (##sys#slot result0 1)) + (lp promise result0))) + (else + (##sys#setslot promise 1 results) + (apply ##sys#values results))))) + ((##sys#structure? val 'promise) + (lp val forward))))) + obj)) (define force ##sys#force) @@ -4823,7 +4835,7 @@ EOF ;;; Promises: (define (##sys#make-promise proc) - (##sys#make-structure 'promise proc #f)) + (##sys#make-structure 'promise proc)) (define (promise? x) (##sys#structure? x 'promise) ) diff --git a/manual/Non-standard macros and special forms b/manual/Non-standard macros and special forms index 728ce3b0..23f69dd9 100644 --- a/manual/Non-standard macros and special forms +++ b/manual/Non-standard macros and special forms @@ -539,6 +539,14 @@ In addition the following feature-identifiers may exist: {{cross-chicken}}, For further information, see the documentation for [[http://srfi.schemers.org/srfi-0/srfi-0.html|SRFI-0]]. +=== {{delay-force}} + +CHICKEN supports the R7RS {{delay-force}} syntax which allows for +iterative lazy algorithms to be expressed in bounded space. + +For more information regarding this behaviour, see the +[[http://srfi.schemers.org/srfi-45/srfi-45.html|SRFI-45]] rationale. + ==== ensure <macro>(ensure PREDICATE EXP [ARGUMENTS ...])</macro> diff --git a/manual/The R5RS standard b/manual/The R5RS standard index 753c70a3..3ca3b73e 100644 --- a/manual/The R5RS standard +++ b/manual/The R5RS standard @@ -507,14 +507,42 @@ execution of <body> may be repeated by invoking the procedure named by <macro>(delay <expression>)</macro><br> The delay construct is used together with the procedure force to -implement lazy evaluation or call by need. (delay <expression>) returns -an object called a promise which at some point in the future may be -asked (by the force procedure) to evaluate <expression>, and deliver -the resulting value. The effect of <expression> returning multiple -values is unspecified. +implement lazy evaluation or call by need. {{(delay <expression>)}} +returns an object called a promise which at some point in the future +may be asked (by the force procedure) to evaluate {{<expression>}}, +and deliver the resulting value. The {{<expression>}} may return +multiple values, which will be correctly memoized and returned by +subsequent calls to {{force}}. This is a CHICKEN extension to R5RS. + +See the description of {{force}} (section 6.4) for a more complete +description of {{delay}}. + +<macro>(delay-force <expression>)</macro><br> + +The expression {{(delay-force expression)}} is conceptually similar to +{{(delay (force expression))}}, with the difference that forcing the +result of {{delay-force}} will in effect result in a tail call to +{{(force expression)}}, while forcing the result of +{{(delay (force expression))}} might not. + +Thus iterative lazy algorithms that might result in a long series of +chains of delay and force can be rewritten using delay-force to +prevent consuming unbounded space during evaluation. + +The {{delay-force}} macro is a CHICKEN extension to R5RS, taken from +R7RS. See the description of force (section 6.4) for a more complete -description of delay. +description of delayed evaluation. + +<procedure>(make-promise obj)</procedure> + +The make-promise procedure returns a promise which, when forced, will +return {{obj}} . It is similar to {{delay}}, but does not delay its +argument: it is a procedure rather than syntax. If {{obj}} is already +a promise, it is returned. + +This procedure is a CHICKEN extension to R5RS, taken from R7RS. ==== Quasiquotation diff --git a/tests/r7rs-tests.scm b/tests/r7rs-tests.scm index f47eacd5..670e9593 100644 --- a/tests/r7rs-tests.scm +++ b/tests/r7rs-tests.scm @@ -77,7 +77,60 @@ (test #t procedure? (force (make-promise (lambda _ 1)))) (test 1 force (make-promise (make-promise 1))) - +;; delay/force/delay-force +(test #t promise? (delay 1)) +(test #t promise? (delay (delay 1))) +(test 1 force 1) +(test force force (force (delay force))) + +(test 3 force (delay (+ 1 2))) ; pp. 18 +(let ((p (delay (+ 1 2)))) + (test '(3 3) list (force p) (force p))) + +(let () ; pp. 19 + (define integers + (letrec ((next + (lambda (n) + (delay (cons n (next (+ n 1))))))) + (next 0))) + (define head + (lambda (stream) (car (force stream)))) + (define tail + (lambda (stream) (cdr (force stream)))) + (test 0 head integers) + (test 0 head integers) + (test 1 head (tail integers)) + (test 2 head (tail (tail integers)))) + +(let () ; later on pp. 19 + (define count 0) + (define p + (delay (begin (set! count (+ count 1)) + (if (> count x) + count + (force p))))) + (define x 5) + (test #t promise? p) + (test 6 force p) + (test #t promise? p) + (set! x 10) + (test 6 force p)) + +(test #t promise? (delay-force 1)) +(test 1 force (delay-force 1)) +(test 6 force (delay-force (+ 1 2 3))) +(test #t promise? (delay-force (delay 1))) + +;; delayed MVs +(call-with-values + (lambda () (force (delay (values 1 2 3)))) + (lambda mv (test '(1 2 3) #f mv))) +(call-with-values + (lambda () (force (delay-force (values 4 5 6)))) + (lambda mv (test '(4 5 6) #f mv))) +(call-with-values + (lambda () (force (delay (values)))) + (lambda mv (test '() #f mv))) (SECTION 6 6) diff --git a/tests/runtests.bat b/tests/runtests.bat index 2bdecf54..c503f153 100644 --- a/tests/runtests.bat +++ b/tests/runtests.bat @@ -382,6 +382,10 @@ echo ======================================== path tests ... %interpret% -bnq path-tests.scm if errorlevel 1 exit /b 1 +echo ======================================== srfi-45 tests ... +%interpret% -s srfi-45-tests.scm +if errorlevel 1 exit /b 1 + echo ======================================== posix tests ... %compile% posix-tests.scm if errorlevel 1 exit /b 1 diff --git a/tests/runtests.sh b/tests/runtests.sh index 43dd3922..16e4bc26 100755 --- a/tests/runtests.sh +++ b/tests/runtests.sh @@ -335,6 +335,9 @@ $interpret -s data-structures-tests.scm echo "======================================== path tests ..." $interpret -bnq path-tests.scm +echo "======================================== srfi-45 tests ..." +$interpret -s srfi-45-tests.scm + echo "======================================== posix tests ..." $compile posix-tests.scm ./a.out diff --git a/tests/srfi-45-tests.scm b/tests/srfi-45-tests.scm new file mode 100644 index 00000000..1950fd39 --- /dev/null +++ b/tests/srfi-45-tests.scm @@ -0,0 +1,93 @@ +;;; Tests adapted from SRFI 45 (for "lazy" -> "delay-force"). +;;; That SRFI Copyright (C) André van Tonder (2003). + +(use (only ports with-output-to-string)) + +(define *errors* 0) + +(define-syntax test + (syntax-rules () + ((_ name expect form) + (let ((ok (equal? expect form))) + (printf "(~a) ~a~n" (if ok "PASS" "FAIL") name) + (when (not ok) (set! *errors* (add1 *errors*))))))) + +(define-syntax output + (syntax-rules () + ((_ . body) (with-output-to-string (lambda () . body))))) + +(test "Memoization test 1" + "hello" + (output (define s (delay (begin (display 'hello) 1))) + (force s) + (force s))) + +(test "Memoization test 2" + "bonjour" + (output (let ((s (delay (begin (display 'bonjour) 2)))) + (+ (force s) (force s))))) + +(test "Memoization test 3" + "hi" + (output (define r (delay (begin (display 'hi) 1))) + (define s (delay-force r)) + (define t (delay-force s)) + (force t) + (force r))) + +(test "Memoization test 4" + "hohohohoho" + (output (define (stream-drop s index) + (delay-force + (if (zero? index) + s + (stream-drop (cdr (force s)) (- index 1))))) + (define (ones) + (delay (begin + (display 'ho) + (cons 1 (ones))))) + (define s (ones)) + (car (force (stream-drop s 4))) + (car (force (stream-drop s 4))))) + +(let () + (define count 0) + (define p + (delay (begin (set! count (+ count 1)) + (if (> count x) + count + (force p))))) + (define x 5) + (test "Reentrancy test 1 (1)" 6 (force p)) + (set! x 10) + (test "Reentrancy test 1 (2)" 6 (force p))) + +(let () + (define f + (let ((first? #t)) + (delay + (if first? + (begin + (set! first? #f) + (force f)) + 'second)))) + (test "Reentrancy test 2" 'second (force f))) + +(let () + (define q + (let ((count 5)) + (define (get-count) count) + (define p (delay (if (<= count 0) + count + (begin (set! count (- count 1)) + (force p) + (set! count (+ count 2)) + count)))) + (list get-count p))) + (define get-count (car q)) + (define p (cadr q)) + (test "Reentrancy test 3 (1)" 5 (get-count)) + (test "Reentrancy test 3 (2)" 0 (force p)) + (test "Reentrancy test 3 (3)" 10 (get-count))) + +(exit *errors*)Trap