0

I began learning Scheme (R5RS) and immediately ran into problems. I have this code:

(define make-source (lambda (seq)
  (define next list)
  (define peek list)
  (let ((seq seq)
        ;(endl (if (null? endl) #f endl))
        )
  (lambda (selector data)
    (cond ((equal? selector 'seq) seq)
          ;((equal? selector "endl") endl)
          ((equal? selector 'next) (car seq))
          ((equal? selector 'peek) (list-ref seq 0))
          (else '()))))))


(define s (make-source "abc"))
(next s)

What I'm trying to do is to create object which can be used to work with strings, lists and vectors like with IO ports. My methods next and peek should work like read-char and peek-char respectively. But every time i try to run it I get

next: undefined;
 cannot reference undefined identifier

What causing the problem? Is it the right way to create object like this? Also, how can I implement optional parameter endl?

Zhurik
  • 126
  • 3
  • 13
  • 1
    `(make-source "abc")` is a function. The definitions of `next` and `peek` are local to, and only exist within, `make-source`, but make little sense as written (define them in your REPL and test them). I suspect that you have skipped over quite a lot in your Scheme book and need to start over somewhere around chapter 1. – molbdnilo Oct 16 '17 at 07:41
  • And `(car seq)` means the same as `(list-ref seq 0)`. Neither applies to strings. – molbdnilo Oct 16 '17 at 07:42
  • @molbdnilo, thanks for reply. I took this post https://stackoverflow.com/questions/2954642/methods-and-properties-in-scheme-is-oop-possible-in-scheme as an example. Can You provide examples of code to fix mistakes, please. – Zhurik Oct 16 '17 at 08:01
  • Not `(next s)` but `(s 'next)`. `make-source` returns a function for message passing. – Sylwester Oct 16 '17 at 22:37

2 Answers2

1

The idea you have tried to implement is normally called message passing. This works by creating a closure scope. In this scope stuff is defined, which is by default private to the scope. At the end of the closure a dispatcher function is returned. The dispatcher takes messages and returns stuff from the captured scope. By this the private stuff gets public. Typically functions are returned. If the caller gets a function, the caller can call the function with any argument, the function accepts.

Your implementation has some cosmetic concerns and some failures.

  1. You should stay with one way defining functions. Either the more verbose syntax:

    (define f (lambda () (display "Hello, World!\n")))
    

    Or the more compact syntax:

    (define (f) (display "Hello, World!\n"))
    

    But do not mix them.

  2. First you have to convert the string into a list in order to use car on the sequence. This is done with the function string->list. You can not use car on a string.

  3. Then you have to implement next and peek to do the right thing. It is not enough to make it aliases to list. The function next consumes a character from the input stream. This means the code most mutate some state somewhere. This requires the use of set!.

  4. The dispatch function compares just symbols. It is easier to compare the symbols with case instead of cond. You can also use cond, but it is more to type.

  5. The sequence of characters is normally limited to the scope of the closure. If you expose the sequence by the seq selector to the calling scope, you violate the idea of encapsulation. This is normally only done for debugging.

  6. If a wrong message is passed to the dispatcher function, it is wise to throw an error instead of returning something useless or undefined.

This shows the code for make-source according to the above suggestions.

(define (make-source seq)

  (let ((seq (string->list seq)))  ;; convert string into list

    (define (next)
      (if (pair? seq)
          (let ((c (car seq)))
            (set! seq (cdr seq))  ;; mutation of the sequence
            c)))

    (define (peek)
      (car seq))

    (define (endl arg1 arg2)  ;; use of the arguments
      (map list->string
           (map (lambda (x)
                  (list arg1 x arg2))
                seq)))

    (lambda (selector)
      (case selector
        ((seq) seq) ;; this is only for debugging
        ;; for correct messages, just return the functions
        ((endl) endl)
        ((next) next)
        ((peek) peek)
        ;; throw error
        (else (error "undefined"))))))

If you call make-source it generates the dispatcher function:

(define s (make-source "abc"))

Now you can pass messages in the form of symbols to the dispatcher.

The following returns the function peeking a character.

(s 'peek)  ;; => <procedure>

In order to execute it, you have to use application twice.

((s 'peek))  ;; => a

You can give it a name, if you feel uncomfortable with the double application.

(define peek-s (s 'peek))
(peek-s)    ;; => a

But you can not define generics this way. A generic is a function called peek instead of peek-s and peeks from any object belonging to the class of source objects. We have defined just peek-s, which peeks just from the s object. In order to define generics, you have to read the Thant Tessman paper "Adding Generics to Scheme". Because of this and other limitations, the above approach is sometimes called poor man's objects. But almost every Scheme has some kind of object system inspired by Common Lisp called CLOS (Common Lisp Object System) or tiny CLOS.

Finally passing arguments to the exposed function is as simple as passing arguments to functions. If you have the exposed function, you can pass whatever the function accepts to it.

((s 'endl) #\< #\>)  ;; => ("<a>" "<b>" "<c>")
ceving
  • 21,900
  • 13
  • 104
  • 178
0

You define next within the scope of make-source. When you call (next s), the interpreter tries to look up next in global scope (global environment), while the interpreter cannot find it because you defined next with the scope of make-scope. This is why the interpreter tells you that "next is undefined".

Your make-scope takes a seq (a sequence I guess?) and returns a lambda expression which takes a parameter. Here is what I did

(define s (make-source '(a b c))
(s 'next 1) 
; Output:
; a
(s 'seq 1) ; 1 could be replaced with any thing actually
; Output
; (a b c)

Hope it helps.

Terence
  • 1
  • 2