stream-{ref,tail,take}: avoid retaining stream for potential error

Although retaining the original stream argument to `stream-ref`,
`stream-tail`, or `stream-take` can enable a better error message if
the stream runs out of elements too soon, it can also interfere with
the intended memory use of a stream.

Closes #2870
This commit is contained in:
Matthew Flatt 2021-04-26 20:03:55 -06:00
parent a2d724fff6
commit f4a9058941
2 changed files with 35 additions and 5 deletions

View File

@ -194,4 +194,27 @@
(stream-lazy (stream-lazy
(stream-lazy '(1))))) (stream-lazy '(1)))))
;; Make sure certain operations that could encounter a too-short stream don't
;; retain the original stream just in case of the error:
(unless (eq? 'cgc (system-type 'gc))
(let ([check (lambda (op)
(define s (stream-cons
1
(stream-cons
2
(begin
(collect-garbage)
(let ([v (weak-box-value wb)])
(stream-cons
3
(stream-cons
v
empty)))))))
(define wb (make-weak-box s))
(test #f 'check-stream-no-retain (op s 3)))])
(check stream-ref)
(check (lambda (s n) (stream-first (stream-tail s n))))
(check (lambda (s n) (stream-ref (stream-take s (add1 n)) n)))))
(report-errs) (report-errs)

View File

@ -103,7 +103,14 @@
(raise-arguments-error 'stream-ref (raise-arguments-error 'stream-ref
"stream ended before index" "stream ended before index"
"index" i "index" i
"stream" st)] ;; Why `"stream" st` is omitted:
;; including `st` in the error message
;; means that it has to be kept live;
;; that's not so great for a stream, where
;; lazy construction could otherwise allow
;; a element to be reached without consuming
;; proportional memory
#;"stream" #;st)]
[(zero? n) [(zero? n)
(stream-first s)] (stream-first s)]
[else [else
@ -120,12 +127,11 @@
(raise-arguments-error 'stream-tail (raise-arguments-error 'stream-tail
"stream ended before index" "stream ended before index"
"index" i "index" i
"stream" st)] ;; See "Why `"stream" st` is omitted" above
#;"stream" #;st)]
[else [else
(loop (sub1 n) (stream-rest s))]))) (loop (sub1 n) (stream-rest s))])))
(define (stream-take st i) (define (stream-take st i)
(unless (stream? st) (raise-argument-error 'stream-take "stream?" st)) (unless (stream? st) (raise-argument-error 'stream-take "stream?" st))
(unless (exact-nonnegative-integer? i) (unless (exact-nonnegative-integer? i)
@ -137,7 +143,8 @@
(raise-arguments-error 'stream-take (raise-arguments-error 'stream-take
"stream ended before index" "stream ended before index"
"index" i "index" i
"stream" st)] ;; See "Why `"stream" st` is omitted" above
#;"stream" #;st)]
[else [else
(make-do-stream (lambda () #f) (make-do-stream (lambda () #f)
(lambda () (stream-first s)) (lambda () (stream-first s))