Well, it’s hard to say what specific practical problem I have. I think I am now comfortable enough with namespaces, requiring, referring, aliasing, and vars, that I have a productive way of working with them.
But I don’t feel I understand them deeply and it was a long road to get to this point. I still have moments when I’m not sure how much code needs to be reloaded so I will restart my entire REPL session rather than try to figure it out.
And I think these difficulties are probably because of how Clojure is different from most other languages in how it handles these things. Consider unusual aspects of Clojure here:
in-ns to change into a namespace is different from using
require to load code into that namespace.
So you can change into a namespace without loading anything, and then it’s like making and changing to an empty directory.
:as option, you can change the name of a namespace when you require it.
This shortened alias can be a convenience. But it also means that I have to choose the alias, so it’s arbitrary and easy to forget. This also creates potential polymorphism at the “package” level (e.g., requiring
It’s called a namespace, but it doesn’t merely define names for values. They are names of Symbols associated with Vars which contain values or else reference types which contain values. Symbols and Vars are both concrete things which come up and need to be directly manipulated from time to time.
Usually I can forget these intricacies and get along fine. I treat everything I
def as a constant and pretend the namespace is just a mapping of names to values, except that I can mutate the mapping by redefining things on the REPL for convenient. But when I don’t forget this complexity, and try to understand the mechanics in detail, it’s dizzying.
For instance, off the top of my head, I am very unlikely to be able to remember the forms which would help me on the REPL with questions like the following: What public defs does this namespace export? What private defs does it not export? How do I reference the private ones? What refs have I already referred into my current ns? What other ns have I required in and with what aliases? Which local unqualified refs come from which ns?
These questions come up a lot in interactive development. They are equivalent to basic command line navigation on a shell prompt, using
cd, and symlinks. So I can handle the fundamental concepts. But I find it hard to keep it straight on the Clojure REPL, and in practice I rely on cider and auto-completion to stumble through. This seems like a symptom of a more fundamental problem, either with my understanding or with the Clojure API functions I mentioned.
As I mentioned, it aggravates matters that Vars are described as one of the concurrency primitives, but in practice they are often just something that you ignore and which mysteriously makes namespaces work. I am exaggerating! — but only somewhat…
Consider also how different Clojure is from most other language. Most languages:
- force a strict association between namespace and directory structure
- force a strict association between namespace and package structure
- do not allow arbitrary renaming of packages/namespaces
- force you to load a package to interact with its namespace, rather than separating the concepts of loading a package and entering or accessing its namespace
- do not declare (or at least do not emphasize) that every single value is contained in a reference type with special concurrency semantics.
Clojure namespaces remind me most of Common Lisp’s module system, which I found very hard to use.
Sorry to be long-winded. Here’s a way to put it that I would bet resonates with the experience of others: I can understand other languages’ package concepts just fine. I can understand file directories and symlinks just fine. So why does the REPL feel harder when I am using it for what are conceptually just packages and directories?