* Revised lazy/force so it actually works:

- `!!' now scans the same kind of data that `make-reader-graph'
    handles (except that hash-tables are not implemented)
  - this means no structs, no mpairs, and a bunch of other stuff
  - `!!!' is gone (lazy procedures are not wrapped)
  - dealing with multiple values moved into lazy/lazy.ss itself (and
    in the future everything will move in there)
* Removed lazy/promise, and use scheme/promise instead.  Also remove
  the docs for lazy/promise that were bogus (since scheme/promise *is*
  doing the same thing now).
* Other adjustments to the docs.  They should be considered incomplete
  now, and will need a major rewrite when the whole thing works again
  (multiple values things are just commented out for now).
* Added a test macro and a quick test suite for lazy/promise.
* The lazy tests are added to the nightly build tests

svn: r11042
This commit is contained in:
Eli Barzilay 2008-08-03 06:28:25 +00:00
parent 545a53731a
commit 6994edd977
6 changed files with 240 additions and 460 deletions

View File

@ -1,77 +1,92 @@
(module force "mz-without-promises.ss"
(require "promise.ss")
(provide (all-defined-except do-!!))
#lang scheme/base
(define-syntax ~ (make-rename-transformer #'lazy))
(define ! force)
(require scheme/promise (for-syntax scheme/base))
(define (!! x) (do-!! x #f))
;; Similar to the above, but wrap procedure values too
(define (!!! x) (do-!! x #t))
;; Force just a top-level list structure, similar to the above.
;; (todo: this and the next assumes no cycles.)
(define (!list x)
(let loop ([x x])
(let ([x (! x)]) (when (mpair? x) (set-mcdr! x (loop (mcdr x)))) x)))
;; Force a top-level list structure and the first level of values, again,
;; similar to the above.
(define (!!list x)
(let loop ([x x])
(let ([x (! x)])
(when (mpair? x)
(set-mcar! x (! (mcar x)))
(set-mcdr! x (loop (mcdr x)))) x)))
;; Force and split resulting values.
(define (!values x)
(split-values (! x)))
;; Similar, but forces the actual values too.
(define (!!values x)
(provide (all-defined-out))
(define-syntax ~ (make-rename-transformer #'lazy))
(define ! force)
(define ~? promise?)
;; force a top-level list structure; works with improper lists (will force the
;; dotted item when it checks if its a pair); does not handle cycles
(define (!list x)
(let ([x (! x)])
(if (list? x) ; cheap check,
x ; and big savings on this case
(let loop ([x x])
(if (pair? x)
;; avoid allocating when possible
(let ([r (loop (! (cdr x)))]) (if (eq? r (cdr x)) x (cons (car x) r)))
x)))))
;; similar to !list, but also force the values in the list
(define (!!list x)
(let ([x (! x)])
(if (list? x) ; cheap check,
(if (ormap ~? x) (map ! x) x) ; and big savings on these cases
(let loop ([x x])
(if (pair? x)
;; avoid allocating when possible
(if (~? (car x))
(cons (! (car x)) (loop (! (cdr x))))
(let ([r (loop (! (cdr x)))])
(if (eq? r (cdr x)) x (cons (car x) r))))
x)))))
(define (!! x)
;; Recursively force the input value, preserving sharing (usually indirectly
;; specified through self-referential promises). The result is a copy of the
;; input structure, where the scan goes down the structure that
;; `make-reader-graph' handles.
(define t (make-weak-hasheq))
(define placeholders? #f)
(define (loop x)
(let ([x (! x)])
(if (multiple-values? x)
(apply values (map ! (multiple-values-values x)))
x)))
;; Multiple values are problematic: MzScheme promises can use multiple
;; values, but to carry that out `call-with-values' should be used in all
;; places that deal with multiple values, which will make the whole thing
;; much slower (about twice in tight loops) -- but multiple values are rarely
;; used (spceifically, students never use them). So `values' is redefined to
;; produce a first-class tuple-holding struct, and `split-values' turns that
;; into multiple values.
(define-struct multiple-values (values))
(define (split-values x)
(let ([x (! x)])
(if (multiple-values? x) (apply values (multiple-values-values x)) x)))
;; Force a nested structure -- we don't distinguish values from promises so
;; it's fine to destructively modify the structure.
(define (do-!! x translate-procedures?)
(define table (make-hash-table)) ; avoid loops due to sharing
(split-values ; see below
(let loop ([x x])
(let ([x (! x)])
(unless (hash-table-get table x (lambda () #f))
(hash-table-put! table x #t)
(cond [(mpair? x)
(set-mcar! x (loop (car x)))
(set-mcdr! x (loop (cdr x)))]
[(vector? x)
(let vloop ([i 0])
(when (< i (vector-length x))
(vector-set! x i (loop (vector-ref x i)))
(vloop (add1 i))))]
[(box? x) (set-box! x (loop (unbox x)))]
[(struct? x)
(let-values ([(type skipped?) (struct-info x)])
(if type
(let*-values ([(name initk autok ref set imms spr skp?)
(struct-type-info type)]
[(k) (+ initk autok)])
(let sloop ([i 0])
(unless (= i k)
(set x i (loop (ref x i)))
(sloop (add1 i)))))
x))]))
(if (and (procedure? x) translate-procedures?)
(lambda args (do-!! (apply x args) #t))
x))))))
;; * Save on placeholder allocation (which will hopefully save work
;; recopying values again when passed through `make-reader-graph') --
;; basic idea: scan the value recursively, marking values as visited
;; *before* we go inside; when we get to a value that was marked,
;; create a placeholder and use it as the mark (or use the mark value
;; if it's already a placeholder); finally, if after we finished
;; scanning a value -- if we see that its mark was changed to a
;; placeholder, then put the value in it.
;; * Looks like we could modify the structure if it's mutable instead of
;; copying it, but that might leave the original copy with a
;; placeholder in it.
(define-syntax-rule (do-value expr)
(let ([y (hash-ref t x #f)])
(cond ;; first visit to this value
[(not y) (hash-set! t x #t)
(let* ([r expr] [y (hash-ref t x #f)])
(when (placeholder? y)
(placeholder-set! y r)
(set! placeholders? #t))
r)]
;; already visited it twice => share the placeholder
[(placeholder? y) y]
;; second visit => create a placeholder request
[else (let ([p (make-placeholder #f)]) (hash-set! t x p) p)])))
;; deal with only with values that `make-reader-graph' can handle (for
;; example, no mpairs) -- otherwise we can get back placeholder values
;; (TODO: hash tables)
(cond [(pair? x)
(do-value (cons (loop (car x)) (loop (cdr x))))]
[(vector? x)
(do-value (let* ([len (vector-length x)] [v (make-vector len)])
(for ([i (in-range len)])
(vector-set! v i (loop (vector-ref x i))))
(if (immutable? x) (vector->immutable-vector v) v)))]
[(box? x)
(do-value ((if (immutable? x) box-immutable box)
(loop (unbox x))))]
[else
(let ([k (prefab-struct-key x)])
(if k
(do-value (let ([v (struct->vector x)])
(for ([i (in-range 1 (vector-length v))])
(vector-set! v i (loop (vector-ref v i))))
(apply make-prefab-struct k
(cdr (vector->list v)))))
x))])))
(let ([x (loop x)]) (if placeholders? (make-reader-graph x) x)))

View File

@ -1,20 +1,6 @@
#lang scribble/doc
@(require (for-label (except-in lazy delay force promise?)
(only-in lazy/force
! !! !!!
!list !!list
split-values !values !!values)
(only-in lazy/promise
delay force lazy promise?)))
@(begin
(define-syntax-rule (def-scheme scheme-force scheme-delay scheme-promise?)
(begin
(require (for-label scheme/promise))
(define scheme-force (scheme force))
(define scheme-delay (scheme delay))
(define scheme-promise? (scheme promise?))))
(def-scheme scheme-force scheme-delay scheme-promise?))
(only-in lazy/force ! !! !list !!list)))
@(define-syntax-rule (deflazy mod def id)
(begin
@ -158,12 +144,6 @@ Similar to @scheme[!], but recursively forces a structure (e.g:
lists).}
@defproc[(!!! [expr any/c]) any/c]{
Similar to @scheme[!!], but also wraps procedures that if finds so
their outputs are forced (so they are useful in a strict world).}
@defproc[(!list [expr (or/c promise? list?)]) list?]{
Forces the @scheme[expr] which is expected to be a list, and forces
@ -176,13 +156,14 @@ Similar to @scheme[!list] but also forces (using @scheme[!]) the
elements of the list.}
@;{ This moved into lazy.ss, and all the other forces will move there too.
@subsection{Multiple values}
To avoid dealing with multiple values, they are treated as a single
tuple in the lazy language. This is implemented as a
@scheme[multiple-values] struct, with a @scheme[values] slot.
@defproc[(split-values [x multiple-values?]) any]{
Used to split such a tuple to actual multiple values. (This may change
@ -199,102 +180,4 @@ Forces @scheme[expr] and uses @scheme[split-values] on the result.}
Similar to @scheme[!values], but forces each of the values
recursively.}
@; ----------------------------------------
@section{Promises}
@defmodule[lazy/promise]
The @schememodname[lazy/promise] module implements lazy promises as
implicitly used by the @schememodname[lazy] language.
Note: this module implements a new kind of promises. MzScheme's
promises are therefore treated as other values---which means that they
are not forced by this module's @scheme[force].
Generally speaking, if you use only @scheme[delay], @scheme[force],
and @scheme[promise?], you get the same functionality as in Scheme.
See below for two (relatively minor) differences.
@scheme[lazy] implements a new kind of promise. When used with
expressions, it behaves like @scheme[delay]. However, when
@scheme[lazy] is used with an expression that already evaluates to a
promise, it combines with it such that @scheme[force] will go through
both promises. In other words, @scheme[(lazy _expr)] is equivalent to
@scheme[(lazy (lazy _expr))]. The main feature of this implementation
of promises is that @scheme[lazy] is safe-for-space (see
@link["http://srfi.schemers.org/srfi-45/"]{SRFI-45} for
details)---this is crucial for tail-recursion in Lazy Scheme.
To summarize, a sequence of @scheme[lazy]s is forced with a single use
of @scheme[force], and each additional @scheme[delay] requires an
additional @scheme[force]---for example, @scheme[(lazy (delay (lazy
(delay (lazy _expr)))))] requires three @scheme[force]s to evaluate
@scheme[_expr].
Note: @scheme[lazy] cannot be used with an expression that evaluates
to multiple values. @scheme[delay], however, is fine with multiple
values. (This is for efficiency in the lazy language, where multiple
values are avoided.)
As mentioned above, using @scheme[delay] and @scheme[force] is as in
Scheme, except for two differences. The first is a
technicality---@scheme[force] is an identity for non-promise values.
This makes it more convenient in implementing the lazy language, where
there is no difference between a value and a promise.
The second difference is that circular (re-entrant) promises are not
permitted (i.e., when a promise is being forced, trying to force it in
the process will raise an error). For example, the following code
(see srfi-45 for additional examples):
@schemeblock[
(let ([count 5])
(define p
(delay (if (<= count 0)
count
(begin (set! count (- count 1))
(force p)))))
(force p))]
returns 0 with Scheme's @|scheme-delay|/@|scheme-force|, but aborts
with an error with this module's promises. This restriction leads to
faster code (see
@link["http://srfi.schemers.org/srfi-45/post-mail-archive/msg00011.html"]{a
SRFI-45 discussion post} for some additional details), while
preventing diverging code (the only reasonable way to use circular
promises is using mutation as above).
@defform[(delay expr)]{Similar in functionality to Scheme's @|scheme-delay|}
@defform[(lazy expr)]{Creates a ``lazy'' promise. See above for
details.}
@defproc[(force [x any/c]) any/c]{
Forces a promise that was generated by @scheme[delay] or
@scheme[lazy]. Similar to Scheme's @|scheme-force|, except that
non-promise values are simply returned.}
@defproc[(promise? [x any/c]) boolean?]{
A predicate for promise values.}
@; ----------------------------------------
@section{MzScheme without Promises}
@defmodule[lazy/mz-without-promises]
The @schememodname[lazy/mz-without-promises] module simply provides
all of @schememodname[mzscheme] except for promise-related
functionality: @|scheme-delay|, @|scheme-force|, and
@|scheme-promise?|. This is because @schememodname[lazy/promise]
defines and provides the same names. It is intended as a helper, but
you can use it together with @schememodname[lazy/promise] to get a
@schememodname[mzscheme]-like language where promises are implemented
by @schememodname[lazy/promise].
;}

View File

@ -52,8 +52,8 @@
;; the exposed `!' (and other similar !s) must be a special form in the lazy
;; language -- but this is achieved through the lazy #%app (~!%app below)
;; that treats it (and the others) specially: uses mzscheme's application
(define-for-syntax strict-forms
(syntax->list #'(! !! !!! !list !!list !values !!values)))
(define-for-syntax strict-names
(syntax->list #'(! !! !list !!list !values !!values)))
;; --------------------------------------------------------------------------
;; Determine laziness
@ -139,6 +139,27 @@
;; to whenever the value is actually forced
(~ (parameterize ([param (! val)] ...) (~begin body ...))))
;; Multiple values are problematic: MzScheme promises can use multiple
;; values, but to carry that out `call-with-values' should be used in all
;; places that deal with multiple values, which will make the whole thing
;; much slower (about twice in tight loops) -- but multiple values are rarely
;; used (spceifically, students never use them). So `values' is redefined to
;; produce a first-class tuple-holding struct, and `split-values' turns that
;; into multiple values.
(define-struct multiple-values (values))
(define (split-values x)
(let ([x (! x)])
(if (multiple-values? x) (apply values (multiple-values-values x)) x)))
;; Force and split resulting values.
(define (!values x)
(split-values (! x)))
;; Similar, but forces the actual values too.
(define (!!values x)
(let ([x (! x)])
(if (multiple-values? x)
(apply values (map ! (multiple-values-values x)))
x)))
(define* (~values . xs) (make-multiple-values xs))
;; Redefine multiple-value constructs so they split the results
@ -236,7 +257,7 @@
(cond [(let ([f #'f])
(and (identifier? f)
(ormap (lambda (s) (module-identifier=? f s))
strict-forms)))
strict-names)))
;; strict function => special forms => use plain application
(syntax/loc stx (f x ...))]
[(toplevel?)
@ -558,7 +579,7 @@
[(pair? l) (cons (car l) (~ (loop (sub1 n) (! (cdr l)))))]
[else (error 'take "not a proper list: ~e" l)])))
;; not like Haskell's `take' that consumes a list
;; not like Haskell's `cycle' that consumes a list
(define* (cycle . l)
(letrec ([r (~ (~append (! l) r))])
r))
@ -682,9 +703,9 @@
;; --------------------------------------------------------------------------
;; Provide everything except some renamed stuff
(define-syntax (provide-strict-forms stx)
#`(provide #,@strict-forms))
(provide-strict-forms)
(define-syntax (provide-strict-names stx)
#`(provide #,@strict-names))
(provide-strict-names)
(define-syntax (renaming-provide stx)
(syntax-case stx ()

View File

@ -1,258 +0,0 @@
;; This module implements "lazy promises" and a `force' that is iterated
;; through them. Scheme promises are not touched: they're used as values.
;; This is similar to the *new* version of srfi-45 -- see the post-finalization
;; discussion at http://srfi.schemers.org/srfi-45/ for more details;
;; specifically, this version is the `lazy2' version from
;; http://srfi.schemers.org/srfi-45/post-mail-archive/msg00013.html and (a
;; `lazy3' variant of `force' that deals with multiple values is included and
;; commented). Note: if you use only `force'+`delay' it behaves as in Scheme
;; (except that `force' is identity for non promise values), and `force'+`lazy'
;; are sufficient for implementing the lazy language.
(module promise "mz-without-promises.ss"
(provide lazy delay force promise?)
;; (define-struct promise (p)) <-- use a more sophisticated struct below
;; Promise records (note: print in meaningful ways like thunks)
(define-values (promise promise? p:ref p:set!)
(let*-values
([(printer)
(lambda (promise port write?)
(let loop ([p (p:ref promise)])
(cond
[(procedure? p)
(cond [(object-name p)
=> (lambda (n) (fprintf port "#<promise:~a>" n))]
[else (display "#<promise:?>" port)])]
;; no values
[(null? p) (fprintf port "#<promise!>")]
[(pair? p)
;; single value
(fprintf port (if write? "#<promise!~s" "#<promise!~a")
(car p))
(when (pair? (cdr p))
(let ([fmt (if write? ",~s" ",~a")])
(for-each (lambda (x) (fprintf port fmt x)) (cdr p))))
(display ">" port)]
[(promise? p) (loop (p:ref p))] ; hide sharing
[(not p) (display "#<promise*active>" port)]
[else (error 'promise-printer "bad promise value: ~e" p)])))]
[(s:promise promise promise? promise-ref promise-set!)
(make-struct-type 'promise #f 1 0 #f
(list (cons prop:custom-write printer)))])
(values promise
promise?
(make-struct-field-accessor promise-ref 0 'contents)
(make-struct-field-mutator promise-set! 0 'contents))))
;; <promise> ::=
;; | (promise <thunk>) delayed promise
;; | (promise (list <object>)) forced promise (possibly multi-valued)
;; | (promise <promise>) shared promise
;; | (promise #f) currently running
;; | (promise <exn>) exception when forced (last version)
;; Creates a `composable' promise
;; X = (force (lazy X)) = (force (lazy (lazy X))) = (force (lazy^n X))
(define-syntax (lazy stx)
(syntax-case stx ()
[(lazy expr) (with-syntax ([proc (syntax-property
(syntax/loc stx (lambda () expr))
'inferred-name (syntax-local-name))])
(syntax/loc stx (promise proc)))]))
;; Creates a promise that does not compose
;; X = (force (delay X)) = (force (lazy (delay X)))
;; = (force (lazy^n (delay X)))
;; X = (force (force (delay (delay X)))) =/= (force (delay (delay X)))
;; so each sequence of `(lazy^n o delay)^m' requires m `force's and a
;; sequence of `(lazy^n o delay)^m o lazy^k' requires m+1 `force's (for k>0)
;; (This is not needed with a lazy language (see the above URL for details),
;; but provided for completeness.)
(define-syntax (delay stx)
(syntax-case stx ()
[(delay expr)
;; see below for using multiple-values:
(syntax/loc stx
(lazy (promise (call-with-values (lambda () expr) list))))]))
;; force iterates on lazy promises (forbid dependency cycles)
;; * (force X) = X for non promises
;; * does not deal with multiple values, since they're not used by the lazy
;; language (but see below)
#; ; this version cannot handle multiple values
(define (force promise)
(if (promise? promise)
(let loop ([p (p:ref promise)])
(cond
[(procedure? p)
(p:set! promise #f) ; mark root for cycle detection
(let loop ([promise* (p)])
(if (promise? promise*)
(let ([p* (p:ref promise*)])
(p:set! promise* promise) ; share with root
(cond [(procedure? p*) (loop (p*))]
[(pair? p*) (p:set! promise p*) (car p*)]
[(promise? p*) (loop p*)]
[(not p*) (error 'force "reentrant promise")]
[else (error 'force
"invalid promise, contains ~e" p*)]))
(begin ; error here for "library approach" (see above URL)
(p:set! promise (list promise*))
promise*)))]
[(pair? p) (car p)]
[(promise? p) (loop (p:ref p))]
[(not p) (error 'force "reentrant promise")]
[else (error 'force "invalid promise, contains ~e" p)]))
;; different from srfi-45: identity for non-promises
promise))
#; ; this version works properly with multiple values
(define (force promise)
(if (promise? promise)
(let loop ([p (p:ref promise)])
(cond
[(procedure? p)
(p:set! promise #f) ; mark root for cycle detection
(let loop1 ([vals* (call-with-values p list)])
(if (and (pair? vals*)
(null? (cdr vals*))
(promise? (car vals*)))
(let loop2 ([promise* (car vals*)])
(let ([p* (p:ref promise*)])
(p:set! promise* promise) ; share with root
(cond [(procedure? p*) (loop1 (call-with-values p* list))]
[(or (pair? p*) (null? p*))
(p:set! promise p*)
(apply values p*)]
[(promise? p*) (loop2 p*)]
[(not p*) (error 'force "reentrant promise")]
[else (error 'force
"invalid promise, contains ~e" p*)])))
(begin ; error here for "library approach" (see above URL)
(p:set! promise vals*)
(apply values vals*))))]
[(or (pair? p) (null? p)) (apply values p)]
[(promise? p) (loop (p:ref p))]
[(not p) (error 'force "reentrant promise")]
[else (error 'force "invalid promise, contains ~e" p)]))
;; different from srfi-45: identity for non-promises
promise))
;; this version deals with multiple values only in `delay' (technicality:
;; actually it doesn't work with `lazy' holding `lazy' of multiple values, so
;; `lazy' works with multiple values unless rewrapped in `lazy'.)
#;
(define (force promise)
(if (promise? promise)
(let loop ([p (p:ref promise)])
(cond
[(procedure? p)
(p:set! promise #f) ; mark root for cycle detection
(let ([vals* (call-with-values p list)])
(if (and (pair? vals*) (null? (cdr vals*)))
(let loop1 ([val* (car vals*)])
(if (promise? val*)
(let loop2 ([promise* val*])
(let ([p* (p:ref promise*)])
(p:set! promise* promise) ; share with root
(cond [(procedure? p*) (loop1 (p*))]
[(or (pair? p*) (null? p*))
(p:set! promise p*)
(apply values p*)]
[(promise? p*) (loop2 p*)]
[(not p*) (error 'force "reentrant promise")]
[else (error 'force
"invalid promise, contains ~e" p*)])))
(begin ; error here for "library approach" (see above URL)
(p:set! promise (list val*))
val*)))
(begin ; error here for "library approach" (see above URL)
(p:set! promise vals*)
(apply values vals*))))]
[(or (pair? p) (null? p)) (apply values p)]
[(promise? p) (loop (p:ref p))]
[(not p) (error 'force "reentrant promise")]
[else (error 'force "invalid promise, contains ~e" p)]))
;; different from srfi-45: identity for non-promises
promise))
;; this version is like the last one, but properly registers
;; exceptions.
;; #;
(define (force promise)
(if (promise? promise)
(let loop ([p (p:ref promise)])
(cond
[(procedure? p)
(p:set! promise #f) ; mark root for cycle detection
(with-handlers*
([void (lambda (e)
(let ([e (if (exn? e)
e
;; make sure it's actually an exception
(make-exn (format "~s" e)
(current-continuation-marks)))])
(p:set! promise e)
(raise e)))])
(let ([vals* (call-with-values p list)])
(if (and (pair? vals*) (null? (cdr vals*)))
(let loop1 ([val* (car vals*)])
(if (promise? val*)
(let loop2 ([promise* val*])
(let ([p* (p:ref promise*)])
(p:set! promise* promise) ; share with root
(cond [(procedure? p*) (loop1 (p*))]
[(or (pair? p*) (null? p*))
(p:set! promise p*)
(apply values p*)]
[(promise? p*) (loop2 p*)]
[(not p*) (error 'force "reentrant promise")]
[else (error 'force
"invalid promise, contains ~e"
p*)])))
(begin ; error here for "library approach" (see above URL)
(p:set! promise (list val*))
val*)))
(begin ; error here for "library approach" (see above URL)
(p:set! promise vals*)
(apply values vals*)))))]
[(or (pair? p) (null? p)) (apply values p)]
[(promise? p) (loop (p:ref p))]
[(exn? p) (raise p)]
[(not p) (error 'force "reentrant promise")]
[else (error 'force "invalid promise, contains ~e" p)]))
;; different from srfi-45: identity for non-promises
promise))
#|
Timing results (#1, #2, #3, #4 are the above versions), in Lazy Scheme:
loop: (define (foo n) (if (zero? n) n (foo (sub1 n))))
(time (! (foo 2000000)))
#1 cpu time: 2067 real time: 2069 gc time: 194
#2 cpu time: 3057 real time: 3058 gc time: 231
#3 cpu time: 2566 real time: 2567 gc time: 235
#4 cpu time: 4744 real time: 4746 gc time: 539
cpu time: 4331 real time: 4334 gc time: 237 ; now with 4.0.1.1
cpu time: 2362 real time: 2361 gc time: 148 ; using scheme/promise
;; what-if call-with-exception-handler was tail-recursive
cpu time: 2357 real time: 2357 gc time: 148
fib: (define (fib n) (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
(time (! (fib 29)))
#1 cpu time: 2196 real time: 2196 gc time: 200
#2 cpu time: 3194 real time: 3195 gc time: 227
#3 cpu time: 2833 real time: 2833 gc time: 231
#4 cpu time: 5837 real time: 5837 gc time: 712
cpu time: 5328 real time: 5329 gc time: 297 ; now with 4.0.1.1
cpu time: 2685 real time: 2688 gc time: 154 ; using scheme/promise
;; what-if call-with-exception-handler was tail-recursive
cpu time: 2579 real time: 2578 gc time: 158
|#
)

View File

@ -0,0 +1,49 @@
#lang scheme/base
(require "testing.ss" lazy/force)
;; lazy/force behavior
(test
(! 1) => 1
(! (! 1)) => 1
(! (~ 1)) => 1
(! (~ (~ (~ 1)))) => 1)
;; !list
(test
(!list (list 1 2 3)) => '(1 2 3)
(!list (~ (list 1 2 3))) => '(1 2 3)
(!list (~ (cons 1 (~ (cons 2 (~ (cons 3 (~ null)))))))) => '(1 2 3)
(!list 1) => 1 ; works on dotted lists
(!list (cons 1 2)) => '(1 . 2))
;; !!list
(test
(!!list (list 1 2 3)) => '(1 2 3)
(!!list (list (~ 1) (~ 2) (~ 3))) => '(1 2 3)
(!!list (list* (~ 1) (~ 2) (~ 3))) => '(1 2 . 3)
(!!list (~ (cons (~ 1) (~ (cons (~ 2) (~ (cons (~ 3) (~ null)))))))) => '(1 2 3)
(!!list (~ (cons (~ 1) (~ (list 2 3))))) => '(1 2 3)
(!!list (~ (cons (~ 1) (~ (list 2 (~ 3)))))) => '(1 2 3))
;; !!
(parameterize ([print-graph #t])
(test
(!! (~ (cons (~ 1) (~ (cons (~ 2) (~ (cons (~ 3) (~ null)))))))) => '(1 2 3)
(format "~s" (!! (letrec ([ones (~ (cons 1 (~ ones)))]) ones)))
=> "#0=(1 . #0#)"
(format "~s" (!! (letrec ([ones (~ (cons 1 (~ ones)))]) (list ones ones))))
=> "(#0=(1 . #0#) #0#)"
(format "~s" (!! (letrec ([x (vector 1 (~ x))]) x)))
=> "#0=#(1 #0#)"
(format "~s" (!! (letrec ([x (vector-immutable 1 (~ x))]) x)))
=> "#0=#(1 #0#)"
(format "~s" (!! (letrec ([x (box (~ x))]) x)))
=> "#0=#&#0#"
(format "~s" (!! (letrec ([x (box-immutable (~ x))]) x)))
=> "#0=#&#0#"
(format "~s" (!! (letrec ([x (make-prefab-struct 'foo 1 (~ x))]) x)))
=> "#0=#s(foo 1 #0#)"
))
(printf "All tests passed.\n")

View File

@ -0,0 +1,70 @@
#lang scheme/base
(require (for-syntax scheme/base scheme/match))
(define-syntax (safe stx)
(syntax-case stx ()
[(_ expr)
(with-handlers ([exn? (lambda (e) #`(list 'error #,(exn-message e)))])
(let-values ([(_ x) (syntax-local-expand-expression
#'(with-handlers ([exn? (lambda (e)
(list 'error
(exn-message e)))])
(cons 'value
(call-with-values (lambda () expr)
list))))])
x))]))
(provide test)
(define-syntax (test stx)
(define (check test blame fmt . args)
(with-syntax ([test test] [blame blame] [fmt fmt] [(arg ...) args]
[loc (string->symbol
(format "~a:~a:~a" (syntax-source blame)
(syntax-line blame) (syntax-column blame)))])
#'(unless test
(error 'loc "test failure in ~e\n ~a" 'blame
(format fmt arg ...)))))
(define (t1 x)
#`(let ([x (safe #,x)])
#,(check #`(and (eq? 'value (car x)) (cadr x)) x
"expected non-#f, got~a: ~e"
#'(if (eq? 'value (car x)) "" " an error") #'(cadr x))))
(define (t2 x y)
#`(let ([x (safe #,x)] [y #,y])
#,(check #'(and (eq? 'value (car x)) (equal? (cadr x) y)) x
"expected ~e, got~a: ~e"
#'y #'(if (eq? 'value (car x)) "" " an error") #'(cadr x))))
(define (te x y)
#`(let ([x (safe #,x)] [y #,y])
#,(check #'(eq? 'error (car x)) x
"expected an error, got ~e" #'(cadr x))
#,(check #'(regexp-match? y (cadr x)) x
"bad error message expected ~e, got ~e" #'y #'(cadr x))))
(let loop ([xs (map (lambda (x)
(if (memq (syntax-e x) '(=> <= =error> <error=))
(syntax-e x) x))
(cdr (syntax->list stx)))]
[r '()])
(let ([t (match xs
[(list* x '=> y r) (cons (t2 x y) r)]
[(list* y '<= x r) (cons (t2 x y) r)]
[(list* x '=error> y r) (cons (te x y) r)]
[(list* y '<error= x r) (cons (te x y) r)]
[(list* x r) (cons (t1 x) r)]
[(list) '()])])
(if (pair? t)
(loop (cdr t) (cons (car t) r))
#`(begin #,@(reverse r))))))
;; test the `test' macro
(test (< 1 2)
(+ 1 2) => 3
(car '()) =error> "expects argument of type"
(if 1) =error> "if: bad syntax"
(test (/ 0)) =error> "expected non-#f"
(test 1 => 2) =error> "expected 2"
(test 1 =error> "") =error> "expected an error"
(test (/ 0) =error> "zzz") =error> "bad error message"
)