ah I see, so it's similar to a block, but with a bit more flexibility
or rather more flexibility on how blocks are used
yeah that sounds reasonable to me
There's one thing I don't understand about namespaces Let's say we have namespace a with function do-a! And it depends on namespace b with function b! And c! do-a! Depends on b! And not on c! That means we created a couple between do-a! And c! Which is not based on the function dependencies I have adopted in a personal project one function only per namespace and it greatly simplified dependencies Is there something I am missing? Maybe better to say functions should be modules and namespaces are just presentation concern for humans but couple operations together unnecessary
i’m not sure i follow
(ns a (:require [b :as b])) (defn do-a! [] (b/b!)) ;; b.clj (ns b) (defn b! [] ...) (defn c! [] ...) The function do-a! Is coupled with c! even though they are not related So its better to have one function per namespace only if functions are depending only on part of the API
so if you have to do set operations, you would prefer to import union and subset only from separate locations to prevent coupling to the function set/insersection?
i’m not sure i’m sure i follow what problem you are trying to solve
Your example is correct but if you multiply this for a lot of modules (which each has their same dependencies) and ship your code as a jar , let's say a small project then you bundle dead code for every function you are not using!
so the problem you are solving is tree shaking? you would prefer to not have set/intersection bytecode on disk?
Yes , why carry around dead code ? If each function would be its own module, each program would have minimal dependencies and be smaller and simpler
> why carry around dead code ? Because it's easy, and in this case "easy" vastly wins over "simple". Each of your dependencies is a huge pile of dead or "dead" code. Same with JVM. Same with every library in your OS that doesn't get used. I used to work with a large code base where each function was in its own module (TS, not CLJ[S]). It was incredibly frustrating. Tasks that should take seconds to fix would take more than a minute just because the context is so fragmented. Navigation is never free. The further apart the code that belongs together is, the harder it is to maintain it.
and i think for java apps is standard to carry the whole weight around ... nothing is omitted from the jars, because you don't know who will be using your jar file and if they expect some classes/functions to be around
and classes are inert on disk until you refer them i believe
clojure files are relatively tiny. one file per namespace is ergonomically very difficult. It creates extra namespaces, extra names, extra boilerplate for managing requires etc.
> That means we created a couple between do-a! And c! Which is not based on the function dependencies
I would say do-a! and c! are not coupled in any important way. You can fairly easily separate them in a mechanical way. Maybe in extreme cases, you would be worried about including c! unnecessarily, but for those cases, you could mechanically apply dead code elimination.
I have found navigation to be simpler as I move the namespaces in a folder so I can see in vim NERDTREE immediately all available functions without opening any files as they are separate. Also when looking at a namespace with only one function inside is simpler to understand and more focused
clojure files are relatively tiny. one file per namespace is ergonomically very difficult. It creates extra namespaces, extra names, extra boilerplate for managing requires etc. _That means we created a couple between do-a! And c! Which is not based on the function dependencies I would say do-a! and c! are not coupled in any important way. You can fairly easily separate them in a mechanical way. Maybe in extreme cases, you would be worried about including c! unnecessarily, but for those cases, you could mechanically apply dead code elimination._ It compounds in huge project, each module bringing more and more stuff in which is not required until you look at >160K LoC application where only 20K is running
From a user perspective, what is the actual problem?
Needing to carry thousands of lines of dead code for every library you only use a slice of
Why would the user care?
Not even considering transitive dependencies
Also, the way clojure libraries are packaged, putting functions in separate namespaces doesn't actually solve the problem of carrying around thousands of lines of dead code.
there are ide features that do exactly what you're saying. just show a list of def’d vars on the left.
> I have found navigation to be simpler as I move the namespaces in a folder so I can see in vim NERDTREE immediately all available functions without opening any files as they are separate.
most IDEs allow you to to type myns/ and it will list the available functions. I find this more ergonomic than using file operations to navigate a project.
but agree with the others the the problem you're optimizing for is the absolute bottom of the barrel of things i care about.
Having dead code and unused dependencies you don't care about?
no. not at the level you're talking anyways. and i run a company that pays per mb for this kind of thing.
iow i'm about as close to someone who cares about this as you'll find, and 1fn per ns solves exactly zero problems for me.
Let's see , I had a matrix4 library and moved all the functions in a matrix4 folder , obviously that folder is still couplings its contents and has to live somewhere So maybe it's a tooling problem
the only person who's gonna care is the person who's counting kb of ram for some reason (i'm also memory constrained but not at the kb level)
I was working with a game engine and needed only a slice of it when I came up with this strategy, got rid of 140 thousands lines
But maybe you're right the folder is still there or we would need to change deployment/repositories
ime game engines revolve more around cache optimization than ram savings?
I was forking it and porting to clojure and all that dead code was bothering me
But otherwise there would have been 500 libraries instead of one ... Hmm 🤔
this is more of a "JVM problem" than a "clojure problem" - kotlin, scala, and java libraries are all bundled in a similar way to what you're describing
You still haven't really explicitly stated the problem you're trying to solve. You implied that dead code is a problem, but haven't explained why.
I have a project called https://github.com/phronmophobic/snowball that let's you see the the sizes and transitive sizes of your dependencies. I can't think of any instance where unused clojure code was significant. It's almost always data resources, media, other languages, or native dependencies.
even for 100k lines of clojure code, assuming 80 bytes per line, that's only 8mb (uncompressed). What's the problem?
Problem is accidental complexity & Change propagation
An application with less code is simpler to reason about , cognitive load
Putting functions in their own namespaces doesn't lead to less code.
I don't know how having more than one function per namespace causes accidental complexity or change propagation. Some examples may help.
It does lead to less code as external dependencies would be more fine grained, the unused code would never be dependent on
I don't know how having more than one function per namespace causes accidental complexity or change propagation. Some examples may help. Let's say you depend on a normal library and need capability X only. But that library also is doing Z and also depending on library Y which itself depends on library alpha. Then you have structurally coupled your code to Y and alpha although the semantics of your program are not connected to those libs. That is why I argue that each form or var should be the module and not namespaces.
That would lead to namespaces being vars and declaring their dependencies
> It does lead to less code as external dependencies would be more fine grained, the unused code would never be dependent on
It wouldn't necessarily be require'd , but it would still be downloaded and shipped around.
I have seen projects that have namespaces that look internal, for example a.implementation. Maybe they are trying to reduce what you have to import.
> Then you have structurally coupled your code to Y and alpha although the semantics of your program are not connected to those libs. Like I said, I don't think this is coupled in any important way. If it was, you could still apply dead code elimination.
I still think there's a missing root cause that could be surfaced with something like the https://en.wikipedia.org/wiki/Five_whys.
That means the var itself should be a namespace For example matrix4.project instead of matrix4/project bundled with other ops You would never depend on unused code if it's not bundled How would that even look like to merge namespaces and vars into one object , interesting! I may have to fork clojure
Each file should only contain one form. (defn matrix4.project {:require [vector.dot vector.scale]} [m v] ...)
Identity & Distribution & dependencies are complected
It sounds like you found a "solution" and are trying to work back to a problem.
Coupling is one thing in software engineering. Cohesion is another. In a good design your modules, packages, classes, namespaces should have a high cohesion. Then the situation that you require only one function from a namespace should not happen so often. If this is the case it could be a sign that the namespace is indeed to big. But one function per namespace means you can never group things. Such a structure is hard to reason about, because there is nothing that helps your brain to structure things. You always have to keep everything in mind. A namespace hierarchy does not solve this problem, because most problems have no hierarchical nature.
I think coupling is the wrong term. Because it doesn't impact your ability to change code without breakage. Like if you delete c! nothing breaks. Therefore there was no coupling to c!. But your question is good with regard to bundling. Like do we need to include c! with the program if it's not used? And also for organization, should c! be grouped with b! if most people who use b! won't want to use c! I would say the grouping is a bit of a matter of ergonomics, discovery and readability. If I want to know what all the math functions are? It's nice to be able to discover them because they're all under the math namespace. And if I am going to use many functions together most of the time, it's nice if they can all be under one require directive. In the bundling question. I think it's trickier. Because each namespace results in overhead in the compiled artifact. So there might be a bit of a balance to achieve. Tree-shaking is likely the ideal solution, as in, just removing whatever isn't used. But I'd be curious if what you are doing really results in a leaning build on average.
You can still group them on an index website: Here are all functions on X: Namespaces are both code storage and human presentation
If I need one concept of a book or one book I don't need the whole library 📚
Namespaces as I understand are syntactic sugar for short names
Yes, a namespace is just a place to group things under for organization. You can have everything under one big namespace. Or everything under a unique namespace. But it's like, why do we have folders and files? I feel it's the same reason we have namespaces. I actually like a big namespace with everything under it, because I can just search inside it easily and see everything together. Some don't. The issue of what code should be included in your build artifact to me is secondary. Even if you don't use a namespace at all, currently it gets included. So it seems your issue is more, why Clojure includes everything and doesn't support a form of tree shaking?
Well, I guess when loading happens Clojure does load the whole namespace and doesn't load namespaces not explicitly required. So it's true maybe it would save on loading times and memory a bit if you broke it out. I still feel tree shaking would be the better solution though.
So it seems your issue is more, why Clojure includes everything and doesn't support a form of tree shaking?JVM clojure. Clojurescript is built around making dead code elimination possible. Presumably, because the costs of shipping unneeded code is higher.
Ya true. I meant if you found bundle size/loading overhead an issue, tree shaking seems the better solution than trying to modularize into really small modules and then filter out unused modules. That said, Java 9 did introduce modules for a similar purpose, so that you can do full JDK+your-app bundles and only have the included modules part of it so it can be a smaller package. So the approach isn't unheard of.
I bet you could trivially implement tree shaking by copying all dependencies (using a build.clk program) and running https://github.com/borkdude/carve to a fixed point
To say, I don't understand what problem here is not fixed by tooling and a build process
What about one jar file per function? That is the next step.
FYI: they asked the same question two months ago (and got much the same answers): https://clojurians.slack.com/archives/C03S1KBA2/p1780981948858489
For what it matters, I think it's a good thought experiment, and trying it out would probably provide some nice learnings. It reminds me of the criticism that clojure.core is too big, and should be more modular, and I think if it was, you could actually get materially faster startup times.
Interesting idea to apply it to clojure.core itself. Then we could group functions also in a folder based on their first Parameter (agents, vectors, collections, maps etc organized by folder name)
(core.collections.associative.operations/assoc planet :name (core.basic-types.string.java-strings.operations.immutable/to-upper "Saturn"))
Reminds me to java. System.out.println. I saw that Java/jvm will introduce value objects in jdk 28 (March 2027) https://openjdk.org/jeps/401 . I hope Clojure benefit from this :)
Clojure 1.13 is moving to JDK 17, so I suspect it will be quite a while before we get a Clojure version based on JDK 28 as a minimum version 🙂
It will benefit, but my guess is that without having to do much of anything
> The Java language should continue to operate on just two kinds of data: primitives and object references. yeah looks like it would "just work" for clojure data types
It seems that numerics would automatically become value objects if you just ran Clojure on newer JDK, but a lot of classes Clojure likely could/should mark "value" you would need the language to adapt.
thanks. At least it will benefit from improved Java library performance if nothing else.