Fork me on GitHub
#architecture
<
2023-02-04
>
Rupert (All Street)14:02:34

Question for library designers: Hypothetically, lets say you have a namespace for sequence related helper functions and a namespace for map related helper functions. You now create a function that takes a sequence and outputs a map (e.g. group-by). Would you put it in the map namespace or the sequence namespace or do something else all together? • Arguments for the sequence namespace: ◦ The input is a sequence - so it's near other functions that also have sequences as inputs. • Arguments for the map namespace ◦ The output is a map - so it's near other functions that also create maps. ◦ In Clojure there is only one output (returned value), but there could be several input arguments. So classifying by output is unambiguous - whilst classifying by input is ambiguous (e.g. group-by takes a function and a sequence). What do you think? I guess this comes down to namespaces are hierarchical - so ultimately there are compromises when things don't neatly fit into that structure.

1️⃣ 2
dominicm15:02:06

Namespaces aren't hierarchical, if that gets you out of your pickle. They're a graph.

Rupert (All Street)15:02:10

Agree they're not strictly hierarchical, but they are mutually exclusive so a function is in namespace A or B (it's confusing if it's in both).

dominicm15:02:12

Yeah, that is true.

dominicm15:02:14

I'd bet that Zach Tellman's book covers it, too

dominicm15:02:11

Personally, I'd be inclined towards one big namespace

☝️ 4
Rupert (All Street)15:02:45

Thanks for the link - will have read through it. Yes, one big namespace could be an option or find the commonality (e.g. sequence and map are both collections).

Noah Bogart15:02:05

We have a 1k line “utils” namespace at my job lol

thom18:02:20

Namespaces, especially for libraries, should align to use cases for consumers or they should be private. There’s no project where I’m working on maps or working on sequences. These shouldn’t be separate.

☝️ 4
pithyless19:02:56

Just to share one datapoint: I've gotten a lot of mileage out of creating namespaces grouped by kind. I find the approach works best if you choose a namespace aliasing convention that identifies the context. For example, if your util namespace has lots of functions like blank-string? or trim-string I would argue that you already felt the need to highlight the context (but you're doing it as a prefix or suffix). So I would make a [app.util.string :as string] and then in your code call string/blank? or string/trim. (Yes, I'm aware of clojure.string, but it's just an example :)) Now, I'm not saying that every noun in my codebase has a separate namespace. There are several different needs for grouping: • clustering around a specific noun/model (e.g. all string and uuid operations), • collecting a set of disjoint alternatives (e.g. different hashing algorithms), • hiding a complex operation (lots of helper functions and one function at the end that is part of the "public api"), • describing some kind of domain hierarchy (parent/child relationships via sub-namespaces), • identifying independent ownership (ie. the reverse-domain conventions) • and so forth. But in practice we only have one technical way to do all of the above: the namespace as segments split by . In a way, it's a form of overloaded polymorphism. And naming is hard. :)

dominicm19:02:04

Supporting that idea and contradicting my own, we have clojure.set and clojure.string.

thom20:02:36

clojure.string seems to exists solely because of the clash with replace. All the regex functions are in core. I’m all for namespaces existing for pragmatic reasons like dealing with external library clashes, but within a single project, especially for something that is probably used in close to 100% of projects, I think you could have just used a prefix.

thom20:02:06

Part of the problem obviously is maintainers and users often have different mental models of libraries. I quite like it when libs go to the effort of packaging things up into a facade namespace of some sort. But if the user mental model is very type-based the it’s fine to reflect that, I guess.

Rupert (All Street)10:02:17

> clojure.string seems to exists solely because of the clash with replace clojure.string namespace was added 2 years after clojure.core string functions (like clojure.core/re-seq). So it might have just been added because they wanted to add bunch of new string functions without cluttering the clojure.core namespace - but didn't want to break backwards compatibility by moving old functions into the new clojure.string namespace. I think a prefix can be useful but would be pretty messy (e.g. clojure.string/upper-case -> clojure.core/string-upper-case ) - if you go down the prefixing route you end up with something similar to PHP's or Excel's function library which aren't the most revered. If you go down the prefix route - you still have the dilemma that I have which is what the prefix should be (e.g. should it be sequence- or map- for a function that takes a sequence and emits a map (like group-by).

Rupert (All Street)10:02:49

Thanks all for the responses. Seems there's no straightforward rule for this - so library writers need to balance the benefits and trade offs on a case by case basis. I would agree with@U05476190 that "naming is hard." !