Qualified Keys
This is a Draft
This is a page about "qualified keys", and why they're so great.
Keys are the markers of associative data structures. We often think about them in terms of maps, like a HashMap, but the fields of types and classes are also keys.
They're a name that allows you to access the mapping of the key, which is the value. And this is almost always done in 2 ways, one with a type/struct/class/record in C/Java/Rust etc., and the other with tables/hashes/objects/dicts in Javascript/Python/Ruby/Lua etc.
The syntax will look something along the lines of x.k, x->k, x[k], x.k(), etc.
There's something interesting about all of those, though... What's k? In a hash table, I have no idea. In a struct, I can look at x, maybe k is an Int. Maybe its a String. If you're lucky it may even be an Any.
That's not particularly helpful though. I know that the k of x can't not be an Int, but I don't know what it actually is. At least I know that though. In a map, it could be anything. Or nothing!
We need definitions of things. "Anything at all, or nothing at all" is just not helpful. Int really very helpful either, but if I know the type of x, and that type has documentation for what its k is, then that's something I can use.
But, I said k of x 4 times, I gave it 3 different types, so I haven't gotten any closer to answering the question of what a k is. Even with documentation on a type, k still has no defined meaning. And how could it? I mean, outside of programming language idioms, "what does k mean?" is just not an answerable question.
A "qualified" key is one with a meaning. One you can reason about. One that will never conflict with anyone else. If I make a key dev.grahp.website.k, and I own grahp.dev (which I do!), then that key is globally unique, and has one meaning. I could put it in 2 different maps without changing its meaning. I could go to definition in my editor, on a map key. If you need a dev.grahp.website.k, and I have a dev.grahp.website.k, I can give it to you! If I have an Int, and you need an Int, that's not the case. If you need a k, and I have a k, we still have a problem. We can only talk through x.
I think people gravitate to types even in languages like Python and Javascript because these languages don't have a way to do this, and you need some kind of foundation of communication. The only way you could make a global key in Javascript is to say const k = Symbol("k"). But there's a bi-directionality here, Symbol("k") != Symbol("k"). If you see a Symbol("k"), you're in exactly the same problem.
You really need language support for this. You need a first-class notion of namespaces, and support namespaces on keys. And you need to be able to define those keys somewhere, somewhere you can go back to later (and gd with your editor). You need a way to alias these keys, so you can say w.k instead of dev.grahp.website.k.
Here's a demonstration in Clojure:
(ns dev.grahp.website
"Defining k")
; ::k is a shorthand
(identical? ::k :dev.grahp.website.k) ;=> true
(require '[clojure.spec.alpha :as s])
; Defines :dev.grahp.website.k to be an int
(s/def ::k int?)
(ns example-usage-of-k
"Using k"
(:require [dev.grahp.website :as w]))
; Likewise, ::w/k is a shorthand
(identical? ::w/k :dev.grahp.website.k) ;=> true
; I'm a function that needs a dev.grahp.website.k
(defn example [{::w/keys! [k]}]
(println k))
(ns some-other-namespace-entirely
"Using k some more!"
(:require [dev.grahp.website :as w]))
; I'm a whole other function that needs the SAME k
(defn example2 [{::w/keys! [k]}]
(inc k))
(require '[clojure.spec.alpha :as s])
(s/valid? ::w/k 42) ;=> true
(s/valid? ::w/k "foo") ;=> false
; clj-kondo is a linter for Clojure
; I'm importing it here so you can see where ::w/k is defined
(add-lib 'clj-kondo/clj-kondo)
(require '[clj-kondo.core :as kondo])
(:keywords (:analysis (kondo/run! {:lint ["src"] :config {:analysis {:keywords true}}})))
;=> ({:filename "src/dev/grahp/website.clj" :row 5 :col 8} ...)