Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by

In clojure, symbols can optionally belong to a namespace:

:FileType/TXT

Is it possible to list all keywords that are defined for a given keyword namespace?

2 Answers

0 votes
by
edited by
 
Best answer

Function that does this implemented as per Andy's answer and comment:

(defn kws-with-ns [ns]
  (try
    (let [tf (.getDeclaredField clojure.lang.Keyword "table")]
      (.setAccessible tf true)
      (let [table (.get tf nil)]
        (->> (.keySet table)
             (filter #(= ns (namespace %)))
             (map #(.get table %))
             (map #(.get %)))))
    (catch Throwable _
      ())))
by
I didn't get to the root cause, but your code did not work for me, and I had used this technique before without ever making changes to modifiers flags of JVM classes before, so offer this alternate function that works for me.  Unlike your code, it can make an exception visible to the caller, and it returns all keywords instead of only those in one particular namespace.   Those changes aren't substantive, just different, but I suspect the getModifiers calls and setInt are completely unnecessary.

(defn all-kws []
  (let [tf (.getDeclaredField clojure.lang.Keyword "table")]
    (.setAccessible tf true)
    (let [table (.get tf nil)]
      (->> (.keySet table)
           (map #(.get (.get table %)))))))

(def x (->> (all-kws)
            (filter #(= (namespace %) "user"))))
by
Yes that was unnecessary, 'setAccessible(true)' is enough. Snippet updated.
+1 vote
by

As an aside, note that the part of such a keyword that is often called a "namespace" need not be an existing namespace at all. The official Clojure documentation (e.g. on this page https://clojure.org/reference/reader ) calls them qualified keywords, and the part before the '/' in the keyword name is the qualifier.

One reason for this is that while the qualifier is allowed to be the same as a namespace in your program, it need not be a namespace at all.

In Clojure/Java, all keywords are 'interned' when read or created dynamically at run time, meaning that all keywords with the same name are the same identical Java object in memory. To implement this, all keywords are stored in a common table. You can see this in the Java file Keyword.java in the Clojure implementation, named table. It is declared private, and has no public methods to access it except via intern to create a new keyword, and find to see whether a keyword with a particular name has already been created.

It is fairly straightforward to use Java reflection APIs to bypass the private restriction, unless your JVM was started with security options that prevent this. One could use those methods to access the contents of that private table field, iterate through all of its entries, and return them all, then you could use Clojure or Java code to filter through them and keep only the ones you are interested in.

I do not know enough about the ClojureScript implementation of keywords to say what is possible there.

by
Trivia:  Equivalent keywords are not necessarily `identical?` in ClojureScript!  There is a special-purpose predicate `keyword-identical?`.
...