How to use lisp packages from libraries?

Pascal Bourguignon pjb at informatimago.com
Mon Jul 5 21:13:31 UTC 2021


Le 05/07/2021 à 22:12, Иван Трусков a écrit :
> What should be done for me to be able to use some package (installed
> with quicklisp) from my ECL library? How should the initialisation be
> done?


There are 3 questions:

- how to use packages?
- how to use quicklisp?
- what initialization is needed?


First about packages.  A package is basically a triple:
- a set of symbols,
- a subset marked for "export",
- a list of used packages.
(plus some details for shadowed symbols and shadowing imports).


Symbols can be created independently from a package (eg. with
MAKE-SYMBOL or with GENSYM), or they can be INTERN'ed in a package,
ie. created having the package as home package, and belonging to the
set of symbols of the package.

It is also possible to import symbols from one package into another
package (so the sets of symbols of packages are not disjoint).  In
that case, the imported symbols keep their original home package.  (If
you import a non-interned symbol, then it's automatically interned,
acquiring the package as home package).

When a package P uses another package Q, then the symbols exported by
Q become visible in P (without being imported or interned).


The important invariant, is that in a given package, the name of the
visible symbols (from packages used, imported or interned) must be
unique.  Therefore packages defined name spaces for symbols.

Two different symbols can have the same name, as long as they're
interned in different packages, and they don't collide thru package
use (export) or import.



Now, the main use of all this is with the lisp reader, which maintains
a current package in CL:*PACKAGE*, that is used for two things:

- this is the package into which unqualified symbols are searched, and 
if not found:
- this is the package into which new symbols are interned.


Note that CL:*PACKAGE* is not used to read qualified symbols.  It is
always possible to write lisp code using only qualified symbols, and
thus becoming entirely independent from CL:*PACKAGE*.  And even more,
using :: instead of : you can use symbols independently from the
export list of the package, since p::s reads the symbol named "S" in
the package named "P", while p:s reads the symbol named "S" in the
package named "P" only if S is exported by P.

     (cl:defun cl-user::echo-string (cl-user::s)
       (cl:coerce (cl:format cl:nil "Received input: ~10A"
                             (cl-user::shorten-string cl-user::s))
                  'cl:base-string))

     (cl:defun shorten-string (cl-user::s)
       (cl:declare (cl:string cl-user::s))
       (cl:let ((cl-user::l (cl:length cl-user::s)))
         (cl:if (cl:> cl-user::l 10)
             (str:concat (str:shorten 7 cl-user::s) (cl:substring (- 
cl-user::l 3) cl:nil cl-user::s))
             cl-user::s)))

Assuming the "STR" package exports "SHORTEN" and "CONCAT".


But it would be inconvenient to have to qualify all the symbols in
lisp code (and some packages have quite long names…).

It will be more convenient, to read the following code, in a package
that uses the "CL" package and the "STR" package:

     (defpackage "MY-LIB"
       (:use "CL" "STR"))
     (in-package "MY-LIB")

     (defun echo-string (s)
       (coerce (format nil "Received input: ~10A"
               (shorten-string s)) 'base-string))

     (defun shorten-string (s)
       (declare (string s))
       (let ((l (length s)))
         (if (> l 10)
             (concat (shorten 7 s) (substring (- l 3) nil s))
             s)))

This answers the first question.  Write your lisp code in your own
package, that you will have carefully designed to use or import all
the symbols you need to make it easy and clear to write your code.



Now there's the question of where does this "STR" package come from?

You seem to indicate that you use quicklisp to load it, and indeed
there's a quicklisp system named "str" which defines a package named
"STR" when loaded.

The question you need to answer is whether you want your program defer
to run-time, on the user's workstation, when to use quicklisp to
donwload and install the "str" system, and load it into your program
to get the "STR" package and its functions?  Do you realize that every
month, new systems are added to quicklisp, but also SOME SYSTEMS are
REMOVED from the quicklisp distribution!?  Do you want your program to
break next month because the quicklisp maintainer decided to remove
some depdency from its distribution?

What if the user hasn't installed quicklisp?

What if he has installed quicklisp, but with a different version of
the "str" system than the one you rely on?

I could give you the indication on how to do that, but this would
clearly be a very bad idea.

What you want, is to obtain the "str" system one way or another;
quicklisp may, today, be a good way to do that, but perhaps not.
Perhaps you need a different version of the STR package (eg. you need
to get the head from the git repository), or perhaps you need to make
your own patches to it.

So I will only assume that you have the sources  of the "str", and
your own sources, "my-lib.lisp" around.

Now, what you should do, is to write an asd file "my-lib.asd" defining
your own asdf system "my-lib" that will cite "str" as a dependency,
and "my-lib.lisp" as a source file.

Then you will use the ecl-provided asdf functions that will load this
system and all the dependencies listed, compile them and link them
into your executable.

At run-time there will be no reference to and no use of quicklisp.




I'd start with "3.1.2.4 Build it as static library and use in C":

https://common-lisp.net/project/ecl/static/manual/System-building.html#Build-it-as-static-library-and-use-in-C

and later move to shared libraries.

https://common-lisp.net/project/ecl/static/manual/System-building.html
https://common-lisp.net/project/ecl/static/manual/System-building.html#Compiling-with-ASDF



Now, in the C code, you should use find-symbol with two arguments.
But if you more than a few functions in your library that you want to
call from C, I would gather them in a vector or a list for easy
access.


     cl_object my_lib_pkg = cl_find_package(make_cl_string("MY-LIB"));
     if(my_lib_pkg==ECL_NIL){ perror("Package MY-LIB is absent"); exit(1); }
     cl_object echo_func = cl_find_symbol(2, 
make_cl_string("ECHO-STRING"),my_lib_pkg);
     if(echo_func==ECL_NIL){ perror("No symbol MY-LIB::ECHO-FUNC"); 
exit(1); }


If you have a lot of functions, you could do something like:

     (defparameter *lib-api* #(initialize echo-string shorten-string  … 
terminate))


     struct {
         cl_object init;
         cl_object echo_string;
         cl_object shorten_string;
         …
         cl_object fini;
     }  api;


     cl_object lib_api = cl_symbol_value(cl_find_symbol(2, 
make_cl_string("*LIB-API*"),my_lib_pkg));

     cl_fixnum i=-1;
     api.init=ecl_elt(lib_api,i++);
     api.echo_string=ecl_elt(lib_api,i++);
     api.shorten_string=ecl_elt(lib_api,i++);
     …
     api.fini=ecl_elt(lib_api,i++);


or even just keep a C vector of cl_object, and index it to retrieve the 
symbol of the function you want to call.




-- 
__Pascal Bourguignon__



More information about the ecl-devel mailing list