5
votes

Is there a way to temporarily import a few functions from a package into the current package, using standard common-lisp functions/macros?

I couldn't find one and had to roll my own. I'd rather not have to code anything up, or introduce another language construct, if the standard already provides such functionality.

(defmacro with-functions (functions the-package &body body)
  "Allows functions in the-package to be visible only for body.
  Does this by creating local lexical function bindings that redirect calls
  to functions defined in the-package"
  `(labels
     ,(mapcar (lambda (x) `(,x (&rest args)
                               (apply (find-symbol ,(format nil "~:@(~a~)" x) 
                                                   ,the-package)
                                      args)))
              functions)
     ,@body))

Example usage:

(defclass-default test-class ()
  ((a 5 "doc" )
   (b 4 "doc")))
#<STANDARD-CLASS TEST-CLASS>
CL-USER> 
(with-functions (class-direct-slots slot-definition-name) 'sb-mop
  (with-functions (slot-definition-initform) 'sb-mop
    (slot-definition-initform
      (car (class-direct-slots (find-class 'test-class))))))
5
CL-USER> 

EDIT: Incorporated some of Rainer's suggestions to the macro.

I decided to keep the run-time lookup capability, at the time cost of the run-time lookup to find the function in the package.

I tried to write a with-import macro that used shadowing-import and unintern, but I couldn't get it to work. I had issues with the reader saying that the imported functions didn't exist yet (at read time) before the code that imported the functions was evaluated.

I think getting it to work with shadowing-import and unintern is a better way to go, as this would be much cleaner, faster (no run-time lookup capability though) and work with functions and symbols in packages.

I would be very interested to see if someone can code up a with-import macro using unintern and shadowing-import.

2
This does not work with lexical functions, since APPLY can't call them using symbols as names. - Rainer Joswig
I don't think this is possible with a macro, because symbols are resolved by the reader before macro are expanded, but I might be wrong I don't fully understand yet how the reader works. - Daimrod
@Daimrod It should be possible to construct such a macro, but it is very tricky (and, probably, unportable), because you will have to handle name conflicts, arising from the early symbol-resolution strategy. - Vsevolod Dyomkin
@VsevolodDyomkin: Ok, but I think it could be done with a reader macro and a code walker but that's sounds like a lot of work for a small improvement to me. - Daimrod
Might it be a better solution for what you're trying to do to switch packages multiple times in the source file? - Rörd

2 Answers

2
votes

It makes runtime function calls much more costly: it conses an arg list, looks up a symbol in a package, calls a function through the symbol's function cell.

It only works through symbols, not lexical functions. That makes it less useful in cases, where code is generated via macros.

Its naming is confusing. 'import' is a package operation and packages deal only with symbols, not functions. You can't import a function in a package, only a symbol.

(labels ((foo () 'bar))
  (foo))

The lexical function name FOO is only in the source code a symbol. There is no way to access the function through its source symbol later (for example by using (symbol-function 'foo)). If a compiler will compile above code, it does not need to keep the symbol - it is not needed other than for debugging purposes. Your call to APPLY will fail to find any function created by LABELS or FLET.

Your macro does not import a symbol, it creates a local lexical function binding.

For slightly similar macros see CL:WITH-SLOTS and CL:WITH-ACCESSORS. Those don't support runtime lookup, but allow efficient compilation.

Your macro does not nest like this (here using "CLOS" as a package, just like your "SB-MOP"):

(defpackage "P1" (:use "CL"))
(defpackage "P2" (:use "CL"))

(with-import (p1::class-direct-slots) 'CLOS
  (with-import (p2::class-direct-slots) 'P1
    (p2::class-direct-slots (find-class 'test-class))))

The generated code is:

(LABELS ((P1::CLASS-DIRECT-SLOTS (&REST ARGS)
           (APPLY (FIND-SYMBOL "CLASS-DIRECT-SLOTS" 'CLOS) ARGS)))
  (LABELS ((P2::CLASS-DIRECT-SLOTS (&REST ARGS)
             (APPLY (FIND-SYMBOL "CLASS-DIRECT-SLOTS" 'P1) ARGS)))
    (P2::CLASS-DIRECT-SLOTS (FIND-CLASS 'TEST-CLASS))))
1
votes

You can use import with a list of qualified symbols (i.e. package:symbol or package::symbol) you want to import and then unintern them.