4

I have written a code which uses lazy evaluation to produce infinite data structure but there is an error.

Here is the code:

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n (force n1)))))
(define (main args)
    (display (car (ints-f 3) ))
    (newline)
    )

This gives an error which says stack overflow. Which means that force is being executed even if not called. How to rectify this?

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n n1))))
(define (main args)
    (display (car (ints-f 3) ))
    (newline)
    )

The above code is giving the expected output of 3 but if I use cdr as in the code below

#!/usr/bin/guile \
-e main -s
!#
(define ints-f 
    (lambda (n) 
        (let ((n1 (delay (ints-f (+ n 1)))))
        (cons n n1))))
(define (main args)
    (display (cdr (ints-f 3) ))
    (newline)
    )

It prints a promise object.

How to use lazy evaluation in guile scheme?

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
Tarun Maganti
  • 3,076
  • 2
  • 35
  • 64
  • 1
    If you `delay` and then `force` immediately after, then you’re circumventing the lazy properties of promises. The `delay` form effectively creates a thunk, and `force` invokes it. The only difference is that promises cache their results, so forcing the same promise twice will not re-evaluate the computation. The semantics of the overall language are still completely strict. – Alexis King Aug 04 '16 at 22:19

2 Answers2

3

Are you trying to create streams? You may wish to consult the (srfi srfi-41) module for an implementation of that. (Disclosure: I wrote the Guile-specific parts of the module code; everything else was ported from the reference implementation.)

(use-modules (srfi srfi-41))
(define-stream (ints-f n)
  (stream-cons n (ints-f (1+ n))))

Note that define-stream and stream-cons are macros that work together to build the (SRFI 45-style) delay/force behind the scenes.

Usage example:

> (stream->list 10 (ints-f 100))
(100 101 102 103 104 105 106 107 108 109)

In particular, your function expands to something like:

(define (ints-f n)
  (lazy (eager (cons (delay n)
                     (lazy (ints-f (1+ n)))))))

which you can use using:

> (define x (ints-f 100))
> (force (car (force x)))
100
> (force (car (force (cdr (force x)))))
101
C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • "expands", or "would expand, if re-written with the `stream-cons`"? OP's function does simple `(cons n (delay ...))`, yes? – Will Ness Aug 06 '16 at 01:25
  • @WillNess `(cons ... (delay ...))` creates odd streams, whereas `(delay (cons ...))` creates even streams. The issue with odd streams is that you always materialise one element too many. With even streams, you can materialise exactly as many elements as you need to. See the Rationale section of [SRFI 41](http://srfi.schemers.org/srfi-41/srfi-41.html) for more details. – C. K. Young Aug 06 '16 at 02:32
  • Yes, I've seen it, but all I saw there was based on a faulty over-eager implementation of `take`. (cf [this](http://stackoverflow.com/questions/38778261/how-to-use-lazy-evaluation-in-guile-scheme/38778442?noredirect=1#comment64969430_38778351)). It is very easy to code correctly, then that objection goes away. One thing is streams where each element is forced independently; another is where getting to an element automatically forces all the previous ones; both have their place; I imagine the latter should be far more commonly used, with the correct `take` of course. – Will Ness Aug 06 '16 at 07:35
  • (the odd streams may also make a coder to have to use explicit delays when coding/calling HOFs like accumulate etc., IIRC, but that's hardly forbidding. --- here's [an example of that, and "properly" coded `take` too](http://stackoverflow.com/questions/23247790/building-accumulator-for-lazy-lists-in-racket/23252817#23252817) --- this also means simple `(lambda() ...)` is good enough a delay --- this simplifies the whole thing, and is bound to be more efficient.) – Will Ness Aug 06 '16 at 08:46
2

The first snippet is incorrect, you're forcing the value when building the sequence, hence defeating the whole purpose of lazy evaluation. On the other hand, the second snippet looks right - although it can be simplified a bit:

(define (ints-f n)
  (cons n (delay (ints-f (+ n 1)))))

It's normal to get a promise when calling cdr, it's a thunk built with delay that will only yield its value when forced. In fact, if you want to peek into the elements of an infinite sequence, you'll have to use a procedure for traversing the part you're interested in:

(define (take seq n)
  (if (= n 0)
      '()
      (cons (car seq)
            (take (force (cdr seq)) (- n 1)))))

Similarly:

(define (get seq idx)
  (if (= idx 0)
      (car seq)
      (get (force (cdr seq)) (- idx 1))))

For example:

(take (ints-f 5) 5)
=> '(5 6 7 8 9)

(get (ints-f 5) 5)
=> 10
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • your `take` makes a classic oversight of over-forcing the sequence by one extra element. `(take seq 1)` should be equivalent to `(list (car seq))`, not `(begin (force (cdr seq)) (list (car seq)))`. :) – Will Ness Aug 06 '16 at 01:19