Why does using keywords or symbols as functions to lookup values from maps work?
Quoting from Joy of Clojure, section 4.3.1--
Because keywords are self-evaluating and provide fast equality checks, they're almost always used in the context of map keys. An equally important reason to use keywords as map keys is that they can be used as functions, taking a map as an argument, to perform value lookups:
(def population {:zombies 2700, :humans 9})
(:zombies population)
;=> 2700
(println (/ (:zombies population)
(:humans population))
"zombies per capita")
; 300 zombies per capita
It is not apparent to me what is going on here. Somehow (:zombies population)
has to get transformed into (get population :zombies)
, right? How exactly does this work? The keyword evaluates to itself, not to a function. Does the reader look out for cases where the first thing in a list is a keyword, and add get and move the keyword to the end of the list?
Citation from official documentation:
Keywords implement IFn for invoke() of one argument (a map) with an optional second argument (a default value). For example (:mykey my-hash-map :none) means the same as (get my-hash-map :mykey :none). See get.
And Clojure can call keyword as function, because it implements same interface as function. The same is for symbols...
Keywords are functions, in every way. There is no reader magic involved, as you will see if you try (let [m {:humans 100}, k :humans] (k m))
. I hope you'll agree there's no way the reader could turn this into a get (the compiler could, but you can pretend that I've made the value of k
depend on an if expression that the compiler can't predict, such as user input).
Because Clojure's core data types are interfaces, and Java objects can implement many interfaces, a piece of data can have multiple types. Is it a keyword? Yes. Is it a function? Also yes:
user> (keyword? :k)
true
user> (ifn? :k)
true
user> (.invoke :k {:k 1})
1