Open favila opened 10 years ago
A sample code to reproduce the issue:
(clojure.edn/read-string (pr-str (into {} [[(keyword "a::b") "a"]])))
java.lang.RuntimeException: Invalid token: :a::b
An even crazier example which is also somehow related:
(clojure.edn/read-string (pr-str (into {} [[(keyword "a b c") "a"]])))
{:a b, c "a"}
In reply to the comment by @raszi ...
For those that are surprised or perhaps confused by the example from @raszi that shows how (clojure.edn/read-string (pr-str (into {} [[(keyword "a b c") "a"]])))
evaluates to {:a b, c "a"}
.
keyword
functionLet's start with some basics. First, let's review the keyword
function. (doc keyword)
gives:
clojure.core/keyword
([name] [ns name])
Returns a Keyword with the given namespace and name. Do not use :
in the keyword strings, it will be added automatically.
I want to call attention to the part that says "a" keyword. To emphasize, only one keyword is returned.
Here are some correct and idiomatic uses of keyword
:
(keyword "foo")
=> :foo
(keyword "bar" "foo")
=> :bar/foo
Here is an obviously incorrect use: (keyword "bar" "foo" "extra")
gives
Execution error (ArityException) at user/eval163 (REPL:1).
Wrong number of args (3) passed to: clojure.core/keyword
keyword
in a perhaps confusing wayNow, let's talk about a valid but perhaps confusing use of keyword
. (I also consider the following example to be non-idiomatic or at least uncommon.) Consider (keyword "x y")
which prints :x y
at the REPL.
Aside: I did not say that (keyword "x y")
evaluates to :x y
-- that would be impossible, since y
is syntactically a symbol and in this context, undefined. You can verify this by trying (resolve 'y)
.
To return to the previous thread, (keyword "x y")
prints :x y
at the REPL. To be clear, :x y
is the printed representation of one keyword with the name "x y". You can easily verify this:
(type (keyword "x y"))
evaluates to clojure.lang.Keyword
.(name (keyword "x y"))
evaluates to "x y"
.If you haven't seen name
before, here is the documentation, available by evaluating (doc name)
at the REPL:
clojure.core/name
([x])
Returns the name String of a string, symbol or keyword.
Similarly, (keyword "x y z")
evaluates to one keyword: :x y z
. This keyword has the name "x y z".
Let's take this from smaller to larger:
Let's look at (a smaller piece of) the example that @raszi mentioned, [(keyword "a b c") "a"]
. Based on what I showed above, we can reason through this and see that this will return a vector with two elements: (i) a keyword with the name ":a b c"
and (ii) the string "a"
. Here is one way to demonstrate this: (clojure.string/join "<$>" [(keyword "a b c") "a"])
evaluates to ":a b c<$>a"
Next, let's look at (into {} [[(keyword "a b c") "a"]])
which prints as {:a b c "a"}
. This is a map with one key, :a b c
and one value, "a"
.
What happens if you type {:a b c "a"}
at the REPL? You'll get (my markdown markup added): "Syntax error compiling at (REPL:0:0). / Unable to resolve symbol: b
in this context". Conclusion: don't type this into the REPL! :)
What should you type at the REPL instead? This: {(keyword "a b c") "a"}
. You might say, "That's much less ambiguous" and you would be right! :)
Next, let's look at (pr-str (into {} [[(keyword "a b c") "a"]]))
which evaluates to "{:a b c \"a\"}"
. Based on number 3, above, if you pass this to clojure.edn/read-string
, you should not expect it to evaluate to the input value!
Using (clojure.edn/read-string (pr-str thing))
is not guaranteed to recover the thing
!
To state it in a different way, #(clojure.edn/read-string (pr-str %1))
is not the identity function. It appears to work that way in a vast majority of common cases, but it breaks down in some 'weird' ways. Caveat evaluator!
Read much more about this issue (and more) on this Clojure Google Group thread: How to escape a space in a keyword?.
EDN spec does not say that consecutive colons are illegal (e.g.
a::b
) even though clojure.reader and clojure.edn won't parse these.(What is the reason for this restriction?)