Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it legal in a function definition to make self-call but illegal for a value?

Structure and Interpretation of Computer Programs (SICP) 3.5.2 introduces infinite streams:

(define ones
  (cons-stream 1 ones))

This code doesn't work in DrRacket, with the error:

ones: undefined; cannot reference an identifier before its definition

Other code like this:

(define (integers-starting-from n)
  (cons-stream n
               (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

produce error:

Interactions disabled

(fall in infinite loop?)

So far as I read(SICP), the key point of implementing an infinite stream is delayed evaluation:

(define (delay exp)
  (lambda () exp))

(define (cons-stream a b)
  (cons a
        (delay b)))

With this used in cons-stream, infinite stream still illegal.

Such data structure reminds me of recursive function in whose definition self-call is legal (in compiling) within or without an actual exit.

Why is it illegal for a value to reference itself? Even the reference has been delayed?

Could infinite stream supported by other programming language?

If not, is it about the way processor deal with assembly language? The data stack stuff?

like image 641
Rahn Avatar asked Jan 08 '23 19:01

Rahn


2 Answers

When you make a procedure the arguments are already evaluated when you start the body of the procedure. Thus delay would not do anything since it's already computed at that time. cons-stream need to be a macro.

DrRacket is not one language implementation. It's an IDE that supports lots of languages. One of them is a SICP compatibility language. I mananged to run your code without errors using this code in DrRacket:

#!planet neil/sicp

(define ones
  (cons-stream 1 ones))

(define (integers-starting-from n)
  (cons-stream n
               (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

It works like a charm. The default language in DrRacket, #!racket, has streams too, but the names are different:

#!racket 

(define ones
  (stream-cons 1 ones))

(define (integers-starting-from n)
  (stream-cons n
               (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

However for Scheme you should use SRFI-41 that you can use from both #!racket (using (require srfi/41)) and #!r6rs (and R7RS-large when it's finished)

(import (rnrs)
        (srfi :41))

(define ones
  (stream-cons 1 ones))

(define (integers-starting-from n)
  (stream-cons n
               (integers-starting-from (+ n 1))))

(define integers (integers-starting-from 1))

To roll your own SICP streams in both #!racket and #!r6rs you can use define-syntax

(define-syntax stream-cons 
  (syntax-rules ()
    ((_ a d) (cons a (delay d)))))

Note that RSFI-41 and rackets own stream library for #!racket delays both values in a stream-cons and not just the tail as in SICP version here.

like image 116
Sylwester Avatar answered May 16 '23 09:05

Sylwester


It's because the expression in a define is evaluated before the name is bound to a value. It tries to evaluate (cons-stream 1 ones) before ones is defined, causing an error.

The reason this works fine for functions is that the body of a function is not evaluated when the function is. That is, to evaluate (lambda (x) (f x)), the language returns a function without looking at its body. Since

(define (f x) (f x))

is syntax sugar for defining a lambda, the same logic applies.

Did you define cons-stream yourself as above? If you make cons-stream a normal function it won't work properly. Since Scheme is strict by default, arguments are evaluated before the function gets called. If cons-stream is a normal function, b would get evaluated completely before being passed to delay, making you hit an infinite loop.

cons-stream in SICP is a "special form" rather than a function, which means that it can control how its arguments are evaluated.

If you use the stream-cons and other stream- operations built into Racket, you'd get the behavior you want.

Finally, some other languages do allow values to reference themselves. A great example is Haskell, where this works because everything is lazy by default. Here's a Haskell snippet defining ones to be an infinite list of ones. (Since everything is lazy, Haskell lists behave just like Scheme streams):

ones = 1 : ones
like image 22
Tikhon Jelvis Avatar answered May 16 '23 08:05

Tikhon Jelvis