Various lazy-related fixes etc.

* A long-standing bug, which happened with
    (let ([x (lazy (delay 1))]) (force x) (force x))
  not being properly handled.

* Added `delay/strict', mostly for the below.

* Made srfi/45 reprovide it as `eager'.

* Also restricted the exports from srfi/45 to its interface.

All of these issues were reported by Andreas Rottmann.
This commit is contained in:
Eli Barzilay 2010-05-20 06:37:24 -04:00
parent 7424735100
commit 12211fff72
5 changed files with 39 additions and 4 deletions

View File

@ -74,6 +74,7 @@
;; follow all sharings (and shortcut directly to the right force)
[(composable-promise? v) (force/composable v)]
[(null? v) (values)]
[(promise? v) (force v)] ; non composable promise is forced as usual
[else (error 'force "composable promise with invalid contents: ~e" v)])))
;; convenient utility for any number of stored values or a raised value.

View File

@ -7,11 +7,21 @@
(define-struct (promise/name promise) ()
#:property prop:force (lambda (p) ((pref p))))
(provide (rename-out [delay/name* delay/name]))
(define delay/name make-promise/name)
(define-syntax (delay/name* stx) (make-delayer stx #'delay/name '()))
;; mostly to implement srfi-45's `eager'
(define-struct (promise/strict promise) ()
#:property prop:force (lambda (p) (reify-result (pref p)))) ; never a thunk
(provide (rename-out [delay/strict* delay/strict]))
(define (delay/strict thunk)
;; could use `reify-result' here to capture exceptions too, or just create a
;; promise and immediately force it, but no point since if there's an
;; exception then the promise value is never used.
(make-promise/strict (call-with-values thunk list)))
(define-syntax (delay/strict* stx) (make-delayer stx #'delay/strict '()))
;; utility struct
(define-struct (running-thread running) (thread))

View File

@ -75,12 +75,19 @@ Returns @scheme[#t] if @scheme[promise] is currently being forced.
Creates a ``call-by-name'' promise that is similar to
@scheme[delay]-promises, except that the resulting value is not
cached. This kind of promise is essentially a thunk that is wrapped
in a way that @scheme[force] recognizes.
in a way that @scheme[force] recognizes.
If a @scheme[delay/name] promise forces itself, no exception is
raised, the promise is never considered ``running'' or ``forced'' in
the sense of @scheme[promise-running?] and @scheme[promise-forced?].}
@defform[(delay/strict body ...+)]{
Creates a ``strict'' promise: it is evaluated immediately, and the
result is wrapped in a promise value. Note that the body can evaluate
to multiple values, and forcing the resulting promise will return these
values.}
@defform[(delay/sync body ...+)]{
Produces a promise where an attempt to @scheme[force] the promise by a

View File

@ -2,7 +2,11 @@
;; scheme/promise has srfi-45-style primitives
(require scheme/promise)
(provide (all-from-out scheme/promise))
(provide delay lazy force
;; Strictly speaking, this should be a procedure according to srfi-45.
;; It's easy to make it one, but then it loses its ability to deal
;; with multiple values (which the srfi completely ignores).
(rename-out [delay/strict eager]))
;; TODO: there is a small difference between the primitives in srfi-45 and the
;; ones provided by scheme/promise (the latter is a bit more permissive). See

View File

@ -136,6 +136,15 @@
(force p) => 3
x => 3)))
(define (test-delay/strict)
(let* ([x 1] [p (delay/strict (set! x (add1 x)) x)])
(test (promise? p)
x => 2
(force p) => 2
x => 2
(force (delay/strict (values 1 2 3))) => (values 1 2 3)
(promise? (force (delay/strict (delay 1)))))))
(define (test-delay/sync)
(letrec ([p (delay/sync (force p))])
(test (force p) =error> "reentrant"))
@ -179,5 +188,9 @@
do (test-basic-promise-behavior)
do (test-printout)
do (test-delay/name)
do (test-delay/strict)
do (test-delay/sync)
do (test-delay/thread)))
do (test-delay/thread)
;; misc tests
(let ([x (lazy (delay 1))]) (force x) (force x)) => 1
))