0
votes

This is a general symbol question in a specific context. The question I think I need to answer is: given a parameter containing a symbol 'foo, how do I manipulate the package part of the symbol so that it is eql to 'package:foo?

The more specific context: There is a unit-testing package, fiveam, that stores test functions in a hash table (it.bese.fiveam::*tests) with keys like package::test-name that hash to an object containing the tests. To run a test, one normally passes to the run! function a symbol that is a hash key: (run! 'package::test-name. If I'm calling run! from the tests package in which I defined the tests, I can just C-x C-e on (run! 'test-name). When calling from the SLIME REPL, I have to do (tests::run! 'tests::test-name), which is a mild inconvenience.

I'd like to run tests from cl-user with (run! 'test-name) and omit the package name. I thought it would be simple to wrap run! and add the missing package to the symbol, but all my tries fail, I guess in part because CL-USER:test-name is a different thing from tests:test-name.

The hash table in question uses eql to test equality. In the tests package:

(eql 'tests::foo 'foo) => T

In CL-USER:

(eql 'tests::foo 'foo) => nil

Various functions can return symbols (e.g. intern, make-symbol or format-symbol) but none of them are eql to the keys in the hash table:

(eql (alexandria:format-symbol t "foo::bar") 'foo::bar) => nil
(eql (make-symbol "foo::bar") 'foo::bar) => nil

Taking this out of the SLIME/REPL/FIVEAM context to generalize where I think the problem is, I can't figure out how to take 'bar and turn it into 'foo:bar as a symbol. I can specify it manually as 'foo:bar, but I can't seem to get anything to eql 'foo:bar, given 'bar.

Note: I don't really care about typing the extra few letters for a package name. What I'm really trying to do here is improve my understanding of scoping and specifying symbols.

Thank you.

2
(find-symbol (symbol-name 'bar) :foo) will return the symbol named BAR in the package named FOO if such a symbol is interned there.jkiiski

2 Answers

1
votes

First off, I assume, that you know, that you can switch to the test package in the SLIME REPL using the extended command in-package (i.e., at the REPL prompt, type a comma , followed by in-package, you will be prompted for a package name), and that switching packages like that is simply inconvenient for you.

One solution could be to introduce a little helper function in whatever package you are currently working in:

(defun run-test (name &optional (package 'test))
   (funcall (find-symbol #.(symbol-name 'run!) package)
            (find-symbol (symbol-name name) package)))

or simpler (and assuming, that the definition of run! came from the fiveam package via :use or :import into the test package)

(defun run-test (name &optional (package 'test))
   (it.bese.fiveam:run! (find-symbol (symbol-name name) package)))

which you can now call as

(run-test 'check-everything)

Note, that we use find-symbol here instead of intern, since we expect the symbols to exist in all cases. If you want the helper to be easily accessible everywhere, you can even do

(defun :run (name &optional (package 'test))
   (it.bese.fiveam:run! (find-symbol (symbol-name name) package)))

and now you can call it as

(:run 'check-everything)

in regardless of the current package. Note, that I would recommend this only for helpers intended to be used in the REPL. Clobbering a global namespace (like the keyword function namespace) like that looks a little fishy to me.

Just for good measure: none of the code given has been tested (or even passed through a REPL) so beware. Also, I heartily recommend reading the chapter on packages and symbols in the CLHS, which should be considered The Ultimate Source™ for these things.

1
votes

You could use the function import to import the symbols you want:

(import '(fiveam:run! my-tests::foo))

This imports both symbols into your current package (you can also give a different package as another argument). You can then

(run! 'foo)

If you try this import after some of your failed attempts, you might land in the debugger, where you can select the appropriate restart to resolve the conflict between symbols of the same name (something like “TAKE NEW”).