3

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
sigint
  • 43
  • 6
  • 1
    You may add functionality similar to that of MIT Scheme https://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/Environment-Operations.html, `environment-define` to be more specific. `eval` may also do the job. – Ivancho Nov 21 '14 at 18:08
  • Thanks for the link. What I am not able to understand is how something like that would be implemented. It can not (at least to my understanding) be based on `define` Is is a special form? – sigint Nov 21 '14 at 18:17
  • Sorry, I did not understand your question well. How did you implement the global environment? I guess it has lists for variable symbols and variable values. I mean you have to access the environment internal representation in order to add new definitions. – Ivancho Nov 21 '14 at 18:29
  • Yes, the global env has variable symbols and values. Do I understand you correctly, there is no way to do implement an ´import´ type function in top level scheme code. It needs a special form implemented into the evaluator? I do not have syntax forms implemented but I guess it could be done that way; but I wanted to avoid that. – sigint Nov 21 '14 at 18:38
  • I'm afraid there is no way to implement `import` as a function without using operations over the environments like the ones in MIT Scheme. – Ivancho Nov 22 '14 at 11:31
  • @Ivancho, thanks. If you put that as an answer. I'd accept it. – sigint Nov 22 '14 at 16:37
  • I think it would be easy to add such functionality to your implementation. You may add to your `eval` procedure a `cond` clause like this: `((tagged-list? exp 'the-enviroment) env)` , which returns the current environment, and start treating the environment as a first class value, meaning you can manipulate its variables and access its enclosing environments up to the global one. – Ivancho Nov 22 '14 at 22:43
  • @Ivancho, thanks again. Much appreciated! I have updated the question to make it cleared. I have first class environments but can not find a way to bind multiple procedures via a `lambda` into the current environment. Do you have any ideas how to do that? – sigint Nov 23 '14 at 19:29
  • You may reuse the procedure you have for defining a new variable in an environment, like `define-variable!`, for your environment values. You do not need the `define` special form, because you have direct access to its underlying mechanism. You may look at `define` and `set!` as some sort of a public interface for the data type of environments. You may skip this interface and use the internals of this data type for your purposes. – Ivancho Nov 23 '14 at 21:56

2 Answers2

1

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)

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Thanks very much. I'd like to upvote but do not have enough reputation :-(. In any case, If I understand the `define-syntax` correctly that will replace the pattern and in that case the define would not be scoped as in my (updated) example. Could you recommend some place to look at how to implement `define-syntax` on top of SICP? That would be great. Thanks. – sigint Nov 23 '14 at 19:32
  • @sigint You don't need `define-syntax` to implement it in an interpreter. Imagine you implement environment as an assoc `((name . value) (name2 . value2) ...)` Then a library definition is just the same `((mylibrary . ((export1 . value1) (export2 value2) ...)) (name . value) ...)` Your library definition actually just evaluates the body and take the list of bindings to export into an assoc and adds that to the environment. Import just makes bindings from that structure. Your environment would only be library binding and perhaps library forms if you want the global environment to be clean. – Sylwester Nov 24 '14 at 07:22
  • Thanks again for your time and patience. I understand all that. The question I do not understand is how to implement `imp` without `define-syntax? – sigint Nov 24 '14 at 08:23
  • @sigint You do it the same way you implement `define` - as a special syntax in your evaluator. – Sylwester Nov 24 '14 at 08:37
  • I think I start to understand. I'll try this out tonight. Thanks. – sigint Nov 24 '14 at 09:08
0

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);
    }
sigint
  • 43
  • 6