[Openmcl-devel] with-package

Pascal J. Bourguignon pjb at informatimago.com
Sat Jan 12 08:27:19 PST 2013


Taoufik Dachraoui <dachraoui.taoufik at gmail.com> writes:

> On Sat, Jan 12, 2013 at 3:12 PM, Pascal J. Bourguignon <
> pjb at informatimago.com> wrote:
>
>     Taoufik Dachraoui <dachraoui.taoufik at gmail.com> writes:
>    
>     > On Sat, Jan 12, 2013 at 2:36 PM, Pascal J. Bourguignon <
>     > pjb at informatimago.com> wrote:
>     >
>     >     Taoufik Dachraoui <dachraoui.taoufik at gmail.com> writes:
>     >
>     >     > Hi
>     >     >
>     >     > I am trying to define a macro as follows:
>     >     >
>     >     > (defmacro with-package ((&rest names) &body body)
>     >     >   `(progn
>     >     >      (use , at names)
>     >     >      , at body
>     >     >      (unuse ,@(reverse names))))
>     >     >
>     >     > The issue is that the body may use symbols defined in one of
>     >     > the names (packages) and not in the current package
>     >
>     >     "Current" WHEN?
>     >
>     >     > How to do this? I tried with eval-when but I do not know
>     >     > how to use
>     >     > it correctly
>     >
>     >     What are the situations available to eval-WHEN?
>     >
>     > Current package when you call with-package 
>    
>     Ok.  When you call with-package, it's at run-time.  And at
>     run-time you
>     can use the current package bound to *package* without any
>     difficulty.
>    
>     With:
>    
>         (defun use*   (syms) (format t "I'll use ~S~%" syms))
>         (defun unuse* (syms) (format t "I unused ~S~%" syms))
>    
>         (defmacro use    (&rest syms) `(use* ',syms))
>         (defmacro unuse  (&rest syms) `(unuse* ',syms))
>    
>         (defmacro with-package ((&rest names) &body body)
>           `(progn
>              (use , at names)
>              , at body
>              (unuse ,@(reverse names))))
>    
>         (defpackage :p1 (:export :*v*))
>         (defparameter p1:*v* 42)
>    
>    
>     we get:
>    
>         cl-user> (let ((*package* (find-package :p1)))
>                    (with-package (a b)
>                      (let ((s (find-symbol "*V*" *package*)))
>                        (print (list s (symbol-value s)))
>                        (terpri))))
>         I'll use (common-lisp-user::a common-lisp-user::b)
>    
>         (*v* 42)
>         I unused (common-lisp-user::b common-lisp-user::a)
>         nil
> […]
>     > ;;; (share '(fn x (+ x 1))) must be evaluated (use calculus), where
>     > share is imported from the package calculus
>    
>     It does.  See above, my macro USE is called at run-time.
> […]
>     WHEN are they interned?  At READ-TIME!
>    
>     Does the operator COMMON-LISP-USER:SHARE use the value of
>     CL:*PACKAGE* at
>     RUN-TIME?
>    
>     See my example, I used CL:*PACKAGE* at run-time twice:
>    
>     - once explicitely by passing *package* to find-symbol.
>    
>     - once implicitely by calling print which uses *package* to
>     determine
>       how to print symbols.
>    
>    
>     Perhaps you could have a look at:
>     http://www.nhplace.com/kent/PS/Ambitious.html
>
> Not exactly what I meant
>
> (use file) ;will create a package <current-dir>/file.lisp with a
> nickname file
> and load the file, then it imports all the external symbol from file
> (it first saves the
> symbols with the same name, if they exist, in the current package
> *package*)
>
> (unuse file) ;; unintern all external symbol of file and restores the
> saved symbols (if they exist)

Ok.  There were a few implicit messages in my posts.

1- A: Because it messes up the order in which people normally read text.
   Q: Why is top-posting such a bad thing?
   A: Top-posting.
   Q: What is the most annoying thing on usenet and in e-mail?
   ----------> http://www.netmeister.org/news/learn2quote.html <-----------
   ---> http://homepage.ntlworld.com/g.mccaughan/g/remarks/uquote.html <---


2- Where is the source of your USE and UNUSE macros? 

   How do you hope we debug your code without providing self-contained
   runnable code?

   But all right, it's not so much a debugging problem than a
   conceptual error you have, but having runnable code would help us
   show you how it's wrong.


3- the code in the body of your with-package macro cannot be read  TIME
   you want (after it is read), but it is read TOO EARLY


4- Kent's paper explains the conceptual problem you have.  Not what you
   want, but what you need to understand.


> I want to find a solution to be able to use with-package such that
> the imported symbols from calculus are interned in the current
> package  before reading and/or compiling the body of with-package

Right.  But no.  At least not using the standard Common Lisp mechanisms
that are involved in such a form.





The REPL is specified as performing a Read Eval Print Loop.  That is
basically:

    (loop (print (eval (read))))

(plus some amenities like printing prompts, printing multiple values,
handing errors, commands, etc).

So clearly, it first reads a whole sexp.  That means, INTERNing the
symbols using the value bound to cl:*package* WHEN it is READ.

Then to evaluate the form, it may have to expand macros WHEN it is
COMPILED (at least by minimal compilation).

Then it evaluates the form that has been read and macro-expanded,
possibly using the values bound to global variables such as cl:*package*
WHEN it is RUN. 

Finally it prints the results.



In the case of LOAD, it is specified to do the same, only while reading
a file or a file-stream.  Basically, the only difference between the
REPL and LOAD, is that during LOAD, *load-pathname* and *load-truename*
are bound, but this is still RUN-TIME (situation :EXECUTE of eval-WHEN).
Otherwise load can also load .fasl files, in which case it's the
situation :LOAD-TOPLEVEL.


And finally, COMPILE-FILE will also read a file, but instead of
evaluating it, it will compile it.  This eval-WHEN situation is
:COMPILE-TOPLEVEL.

Notice here a very important sentence: COMPILE-FILE WILL NOT EXECUTE THE
CODE THAT IT COMPILES!  (that is, unless you wrap it in a (eval-when
(:compile-toplevel) …) of course).



You have several tools and several solutions to your problem.

 1- there are reader macros.
 2- there are macros.
 3- there are functions.
 4- there is EVAL-WHEN.
 5- there is evaluation at (source) read-time.  (reader macro, #.)
 6- there is evaluation at macro-expansion time (macro)
 7- there is evaluation at compilation-time
    (eval-when :compile-toplevel).
 8- there is evaluation at when the binaries (.fasl) are read
    (eval-when :load-toplevel).
 9- there is evaluation at when the source (.lisp) are read
    (eval-when :execute).
10- there is evaluation at run-time.



But given that INTERN (when called by READ) uses the value of the
variable CL:*PACKAGE* at READ-TIME, and that setting or binding a
variable can only be done at RUN-TIME, you need to shift a RUN-TIME into
READ-TIME, or _BEFORE_ READ-TIME of the concerned forms.



Notice how the REPL or LOAD loops will intercalate READ-TIME,
COMPILATION-TIME (MACRO-EXPANSION TIME) and RUN-TIME.

       (loop                     ; "repl"-time
          (print                 ; "repl"-time
              (eval              ; run-time
                                 ;  which includes a:
                                 ;    compilation-time (at least minimal
                                 ;                     compilation)
                                 ;    which includes a:
                                 ;      macro-expansion time
                (read))))        ; read-time


so it goes:

   read-time
   macroexpansion-time
   compilation-time
   run-time
   … print
   … loop
   read-time
   macroexpansion-time
   compilation-time
   run-time
   … loop
   read-time
   macroexpansion-time
   compilation-time
   run-time

etc.


So you can first read a form that will set the CL:*PACKAGE* variable
during its run-time.

And then read ANOTHER form that will use the CL:*PACKAGE* variable
during its read-time.


For example:

    (in-package "CALCULUS")

    (share '(f x y))

    (in-package "COMMON-LISP-USER")

    (print 'hello)



Notice what in-package expands to:

    (macroexpand '(in-package "CALCULUS"))
    --> (eval-when (:execute :load-toplevel :compile-toplevel)
          (setf *package* (find-package "CALCULUS")))
        t


In particular, it is evaluated at compilation time, so that the next
forms read by the compiler are read by interning in the new package!



So you can just write in your files:

    (use "CALCULUS")
    (share '(fn x (+ x 1)))
    (unuse "CALCULUS")





Using reader macros, you could execute something like (use "CALCULUS")
at read-time, _BEFORE_ reading the "body".

For example, you could write a reader macro on #\{ and use it as:

    { "CALCULUS"
      (share '(fn x (+ x 1)))
    }

This is not a very good idea, because reading this would have those side
effects of USE and UNUSE.   Editors and other tools may need to read
forms several times, there may be other threads using the current
package, etc, so side effects at read-time are even worse than at
run-time.



    (defparameter *pstack* '())

    (defun use* (pname)
      (push *package* *pstack*)
      (setf *package* (find-package pname)))

    (defun unuse* (pname)
      (declare (ignore pname))
      (setf *package* (pop *pstack*)))



    (set-macro-character #\{ 'read-with-package nil)

    (set-syntax-from-char #\} #\))

    (defun read-with-package (stream ch)
      (declare (ignore ch))
      ;; This is READ-TIME !
      (let ((pname (read stream)))  
        (use* pname) ; Side effect at read-time, BAD!
        (unwind-protect ; against further read errors.
             (let ((form (read-delimited-list #\} stream)))
               (if (= 1 (length form))
                   form
                   `(progn , at form)))
          (unuse* pname))))

    (defpackage :calculus (:use :cl))

    cl-user> '{"CALCULUS" (share '(fn x (+ x 1)))}
    (calculus::share '(calculus::fn calculus::x (+ calculus::x 1)))

    cl-user> '{"CALCULUS" (share '(fn x (+ x 1))) (print 'share)}
    (progn
       (calculus::share '(calculus::fn calculus::x (+ calculus::x 1)))
       (print 'calculus::share))


But notice that this is only a read-time operation.  When the forms read
are evaluated, we are back to the run-time binding of *package*.  Which
is the opposite of what you asked first.  (But that's all right, you
didn't know what you were asking).



       

Now, you could also define a reader macro for #\( so that you could
maskerade a form like (with-package ("CALCULUS") …) as a normal lisp
operator form, while being a read-time reader macro.  It's possible but
it would be very very bad, because it would mislead any seasonned
lisper.  (On the other hand, seasonned lispers should be ready to be
misled this way at any time >:-}).




There are also some implementations that expand the symbol qualification
syntax to let you read things as:

     'calculus::(share '(fn x (+ x 1)))
     --> (calculus::share '(calculus::fn calculus::x (+ calculus::x 1)))

You could implement reader macros to do that portably, you would only
have to define a reader macro on all the characters that may start a
package name.  (Or use a future lisp reader hook when it'll be specified
and implemented ;-)).


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
A bad day in () is better than a good day in {}.




More information about the Openmcl-devel mailing list