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.
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.
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.
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!
.
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.
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.
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>")