Tricky keywords

Unlike symbols, keywords in Clojure always evaluate to themselves.

:a-keyword
=> :a-keyword

So one can always tell whether a map contains a keyword by looking at the map. For example the map {:a 1} obviously contains the keyword :a and does NOT contain the keyword :b.

Let’s look at a different example. Say I gave you a map called operations - a mapping between numerical operations and their names which when evaluated looked like this:

operations
=> {:+ "addition", 
    :- "subtraction", 
    :* "multiplication", 
    :/ "division"}

Seems reasonable enough:

(:+ operations-map)
=> "addition"

But then something strange happens:

(:/ operations-map)
=> nil

Wat the .. ?!

(keys operations-map)
=> (:* :- :/ :+)

The key appears to be in the map…

(assoc operations-map :/ "I don't get it")
=> {:+ "addition", 
    :- "subtraction", 
    :* "multiplication", 
    :/ "I don't get it",
    :/ "division"}

A map with two identical keywords?! This can’t be …

(keys (assoc operations-map :/ "I still don't get it"))
=> (:* :- :/ :/ :+)

Wait a minute..

(map name (keys operations-map))
=> ("*" "-" "" "+")

What sorcery is this?

(map namespace (keys operations-map))
=> (nil nil "" nil)

Wait, are you saying?

(keyword "/")
=> :/
(keyword "" "")
=> :/

I see …

(= :/ (keyword "/"))
=> true
(= :/ (keyword "" ""))
=> false

So keywords can have empty string as their name?

(keyword "")
=> :

Any string really:

(keyword ":")
=> ::

Also as their namespace:

(keyword "" "a")
=> :/a

And when added to a map it’s printed using the namespaced map syntax:

(assoc {} (keyword "" "a") 1)
=> #:{:a 1}

To be clear, :, ::, :/a and #:{:a 1} are not valid Clojure and cannot be read by the reader.

Naturally :/ is read as a namespace-less keyword named slash:

((juxt namespace name) :/)
=> [nil "/"]

Conclusion

Don’t trust maps or keywords given to you by strangers! :-)

P.S.

Another curious thing: The reference says “Keywords cannot contain ‘.’ in the name part, or name classes”. But at least on Clojure 1.10.1 it seems to work:

(name :.)
=> "."
(name :a.b)
=> "a.b"
(name :a.b/c.d)
=> "c.d"
Jordan Biserkov About Blog Oss Links Projects

03 May 2020


Email (prefered comm)
Jordan@Biserkov.com
Call (UTC+1)
+47 76 526 00 50
+359 88 602 22 93
Follow me on:
Mastodon: @jbiserkov
Twitter: @jbiserkov