18

Q: How does one create and use :keywords?

Consider a (naive, apparently) attempt to access the following toy alist:

(setf alist '((:key-1 "Key no. 1")
              (:key-2 "Key no. 2")))

(assq :key-1 alist)                 ; => (:key-1 "Key no. 1")
(assq (make-symbol ":key-1") alist) ; => nil

The first key works as expected, but the second key does not. Insofar as there is no obvious make-keyword function, how does one create and use a keyword?

Original motivation: I need to transform a string into a lookup key that is a symbol onto which I can put properties.

In the process of formulating this question, I've gotten to at least part of the answer, which I'm posting separately. I'm hoping brighter minds than mine can improve upon it.

Dan
  • 32,584
  • 6
  • 98
  • 168

3 Answers3

10

You are right that make-symbol will create a keyword that is not eq to any existing keyword, and intern might pollute the global obarray with the new symbol. In between those, you have intern-soft, which returns the symbol if it has already been created, or nil if it hasn't:

ELISP> (intern-soft ":key-1")
nil
ELISP> :key-1
:key-1
ELISP> (intern-soft ":key-1")
:key-1

This should be suitable for your purpose: if the keyword doesn't exist, it cannot be present in the alist, so there is no need to create it just to check whether it's there. Something like:

(let ((maybe-keyword (intern-soft ":key-1")))
  (and maybe-keyword (assq maybe-keyword alist)))
legoscia
  • 6,012
  • 29
  • 54
  • Clever -- I'd seen `intern-soft` but hadn't thought to use it this way. – Dan Feb 27 '15 at 18:24
  • 1
    This is a great solution but be careful to call `intern-soft` *after* any other code has run, which should have interned those keywords. I ran into a problem using macros, interning the symbols before the actual parsing code ran, which then re-interned the symbols, causing a mismatch. Regardless, thanks to @legoscia for this nice solution! – hraban Aug 23 '23 at 02:14
8

Perhaps I'm not understanding the question correctly. But if you want a keyword (i.e., you want to satisfy keywordp) then you want the symbol to be interned in the global obarray, obarray.

It must be interned there to satisfy keywordp, AFAICT, and C-h f keywordp says so.

So the answer to your question, AFAICT, is just to use intern.

I feel like you are perhaps not asking your real question - seems like an XY question. What are you really trying to do? (Maybe pose that as a separate question.)

[In answer to your comment that interning "is not :keyword specific, as it applies to all symbols": Correct, interning is not specific to keywords. But interning (in obarray) and using a symbol-name that starts with : is specific to keywords.]

Drew
  • 75,699
  • 9
  • 109
  • 225
  • It's most certainly an XY question in the sense that I'm trying to Y, but got really interested in X in my attempt to get to Y. Let me give some thought to how to pose the question about Y in such a way that it's useful to others as well. – Dan Feb 27 '15 at 18:26
  • Great. That will be helpful. Thx. – Drew Feb 27 '15 at 20:45
3

Here's a partial answer to this question. The short and not entirely satisfying version seems to be: use intern.

:key-1 satisfies both:

(symbolp :key-1)                    ; => t
(keywordp :key-1)                   ; => t

While (make-symbol ":key-1") satisfies the first but not the second:

(symbolp (make-symbol ":key-1"))    ; => t
(keywordp (make-symbol ":key-1"))   ; => nil

Now, the docstring for make-symbol says that it will:

Return a newly allocated uninterned symbol whose name is NAME.

Mmmkay, and the docstring for keywordp says that it will:

Return t if OBJECT is a keyword. This means that it is a symbol with a print name beginning with : interned in the initial obarray.

So it appears that intern will work:

(assq (intern ":key-1") alist)      ; => (:key-1 "Key no. 1")

Because intern will:

Return the canonical symbol whose name is STRING. If there is none, one is created by this function and returned. A second optional argument specifies the obarray to use; it defaults to the value of obarray.

But this doesn't seem to be :keyword specific, as it applies to all symbols. By default, it also appears to pollute the global obarray, which may or may not be A Big Deal.

Dan
  • 32,584
  • 6
  • 98
  • 168