Simple library mechanism for scheme - import implementation

377 views Asked by At

I implemented a basic scheme (think SICP). Now, I'd like to add a basic import/library functionality but have trouble coming up with a way to do that. I considered two approaches so far both suffering from the same obstacle.

In the old SICP (edition one) book there was a make-environment / package chapter which was also discussed here. This returns a new environment.

I would like to call something like

(import lib)

where lib either provides an environment or a list of procedure names and procedures. The question I have is how to programmatically extend the current environment with procedures that are provided by the library. Using something like a

((lambda (name proc) (define name proc)) 'test (lambda a (+ a a)))

is not going to work since define can not create a binding that is outside the scope of the lambda.

I have looked at the reference implementation of r6rs but was not able to figure out what the underlying mechanism for import is. How does it create the binding?

update 1

I think the underlying question (issue) I have is that it's not possible to use define within a lambda since the modification of the environment done through define is limited to the scope of the surrounding lambda. Is there a why programmatically define multiple (for example generated) procedures.

This works (similar to what is described here):

((eval `square-rrot scientific-lib) 4)

and this

(eval `(square-rrot 4) scientific-lib)

I can even write

(define sqrt (eval `square-root scientific-lib))

The above, however, is not sustainable, If I have a lib with 100 functions I can not define them one by one, I'd need a programmatic way to do that but I can not use something like:

((lambda (newName, libName) (define newName (eval libName scientific-lib))) sqrt `square-root)

It seems to me after reading the comments and answers that it is not possible based in stuff presented in SIPC. One needs more advanced stuff like define-syntax. Or am I wrong?

2

There are 2 answers

0
sigint On BEST ANSWER

Here is how I did it in the end. Basically I departed from scheme and added a flag to lambda. A lambda can then either be scoped or not. The scoped lambda behaves as the usual lambda. In the case the lambda is not scoped I evaluate the body in an empty environment. This is then evaluated in the current environment. Something like this:

    if (lambdaHasAttribute(lambda, SCOPED)) {
        eenv = environment_extend(parameter, arguments, lambda_env);
        tmp = eval(lambda_body, eenv);
    } else {
        eenv = environment_extend(parameter, arguments, , mk_environment());
        /* scope arguments */
        tmp = eval(lambda_body, eenv);
        /* evaluate unscoped */
        tmp = eval(tmp, global_env);
    }
5
Sylwester On

So your library just needs to become a bunch of evaluated forms that has a local environment like in the definition. The value produced by the library would be some sort of object added to a global list of available libraries that has names together with their values (procedures, syntax, ..) to be exported.

An import usually have tons of features but in reality it only takes a library object and inserts the binding it wants into a frame and puts the body of the program / library withing that frame.

You could make a crude library implementation in Scheme but as long as you cannot alter environment frames as easily you need to name what you need to import:

;; crude library support made with 
;; syntax rules and closures
#!r6rs
(import (rnrs base)
        (only (srfi :1) filter any))

(define-syntax lib
  (syntax-rules ()
    ((_ name (export-symbols ...) body ...)
     (define name
       (let ()
         ; make the defines local
         ; here you import other libraries
         body ... 

         ;; ^binds is a assoc between symbols and
         ;; implementation. 
         (define ^binds
           (list (cons 'export-symbols export-symbols) ...))

         ;; the function to leak definitions
         (lambda args
           (apply values 
                  (map cdr 
                       (filter 
                        (lambda (x)
                          (any (lambda (e)
                                 (eq? e (car x)))
                               args))
                        ^binds)))))))))

(define-syntax imp
  (syntax-rules ()
    ((_ name sym1)
     (define sym1 (name 'sym1)))
    ((_ name symn ...)
     (begin
       (imp name symn) ...))))

;; test
(lib test (add sub)
     (define (add a b)
       (sub a (sub 0 b)))
     (define (sub a b)
       (- a b)))

(imp test add)
(add 5 3) ; ==> 8

This does not look like much but add uses sub from the same library which is not available globally since we didn't import it. You see? You can even have private (not exported) procedures as well.

It's main workings is the procedure to leak parts of the closure made by the library form and it's the same idea as behind message passing. (one way to do OOP in Scheme)