Search code examples
ioschemeracket

How to reference list from input file


There are lots of manual how to make an input in scheme, but I do not understand how to work with it. Lets say we have simple txt file as an input a.txt:

1 2 3

I am calling standard io procedure on it

(let ((p (open-input-file "a.txt")))
  (let f ((x (read p))) ; reading from file
    (if (eof-object? x) ; check for eof
    (begin
      (close-input-port p)
      '()
      )
   (cons x (f (read p))))))

output:

'(1 2 3)

Which is something I am looking for, but if I want to make standard pair/list manipulations on it I do not get what I expect:

(let ((p (open-input-file "a.txt")))
  (let f ((x (read p))) ; reading from file
    (if (eof-object? x) ; check for eof
    (begin
      (close-input-port p)
      '()
      )
   (cdr 
      (cons x (f (read p)))))))

output:

'()

How do I get a hold of rest of the list?


Solution

  • Notice that the list you want to manipulate is constructed by the (let ...) expression, ie:

    (let ((p ...)) ...)                       ;; => '(1 2 3)
    

    So, to get the "rest" of the list, you have to apply cdr to the entire let expression, ie:

    (cdr (let ((p ...)) ...))                 ;; => '(2 3)
    

    There is a difference to applying cdr outside the let expression as opposed to within. When you apply it outside, the let expression first gets evaluated to a list, which cdr then manipulates to get its "rest" values. This is equivalent to writing:

    (cdr '(1 2 3))                            ;; => '(2 3)
    

    But if you apply it the way you have within its body, then at every recursive call for f, which is where a single cons cell is appended recursively to the list-so-far, you end up chaining a cdr to it, so instead of constructing a chain of cons as follows:

    (cons 1 (cons 2 (cons 3 '())))                            ;; => '(1 2 3)
    

    you instead construct a chain of the form:

    (cdr (cons 1 (cdr (cons 2 (cdr (cons 3 '()))))))          ;; => '()
    

    To see how this happens, it is a good idea to either think mentally about, or write down, the execution flow of your program. This enables you to understand what your expressions would evaluate to.

    For example, your first let expression can be rewritten to an equivalent function as follows:

    (define p (open-input-file "a.txt"))
    
    ;; (let f (...) ...) is a named let,
    ;; and is equivalent to the function below.
    (define (f x)
      (if (eof-object? x)
          '()
          (cons x (f (read p)))))
    
    (f (read p))
    
    (close-input-port p)
    

    and its execution flow would look somewhat as follows:

    (f (read p))
    => (f 1)
    => (cons 1 (f (read p)))
    => (cons 1 (f 2))
    => (cons 1 (cons 2 (f (read p))))
    => (cons 1 (cons 2 (f 3)))
    => (cons 1 (cons 2 (cons 3 (f (read p)))))
    => (cons 1 (cons 2 (cons 3 (f eof))))
    => (cons 1 (cons 2 (cons 3 '())))    ;; => '(1 2 3)
    

    but if you apply cdr to your recursive call as follows:

    (define (f x)
      (if (eof-object? x)
          '()
          (cdr (cons x (f (read p))))))
    

    then the execution would change to the following:

    (f (read p))
    => (f 1)
    => (cdr (cons 1 (f (read p))))
    => (cdr (cons 1 (f 2)))
    => (cdr (cons 1 (cdr (cons 2 (f (read p))))))
    => (cdr (cons 1 (cdr (cons 2 (f 3)))))
    => (cdr (cons 1 (cdr (cons 2 (cdr (cons 3 (f (read p))))))))
    => (cdr (cons 1 (cdr (cons 2 (cdr (cons 3 (f eof)))))))
    => (cdr (cons 1 (cdr (cons 2 (cdr (cons 3 '()))))))
    => (cdr (cons 1 (cdr (cons 2 '()))))
    => (cdr (cons 1 '()))
    => '()