Question: Can modularity (drawing sensible "bounda...
# thinking-together
n
Question: Can modularity (drawing sensible "boundaries" between units of code and/or data) be achieved without hierarchy? Discuss. πŸ™‚
d
Modularity (separating things into separate "wholes") is not necessarily related to there being any hierarchy. I think a lot of hierarchy comes into trying to shove like things into a "common base" and then subclass. But on that matter I'll just say: composition over inheritance, interfaces over subclasses, labels over a tree. "A city is not a tree". Some things fit multiple categories, or can play multiple roles, and that does not slice cleanly into exclusive buckets
❀️ 1
πŸ’― 4
n
Traditional approaches to modularity certainly induce hierarchy. In a typical program you have "modules" containing "functions" containing nested functions, loops, or other kinds of scopes. And that has nothing to do with OOP!
I'm discovering how complicated a question this is. I'm not even ready to provide a response myself yet! I'll respond a bit later πŸ™‚
d
Anyway, breaking things into parts does NOT mean you have modularity -- less likely if there's a hierarchy involved. Modularity is about how "whole" those pieces are: Do they make sense in isolation, or are they highly dependent / interconnected with each other? I'll add more in a bit, I have to step away
Each module should be it's own world -- not a fragment of a larger picture. However, it may be an inhabitant of a larger world, or may have inhabitants of its own. But it should have no knowledge of the outside world, or of the inside world of its inhabitants. It may know if the interface-boundary of its inhabitants, but without their knowledge.
πŸ’― 1
❀️ 1
So for example, a Car may have an engine and doors and wheels, and might have behaviors (methods) like drive forward, turn left, etc. But "pick up the kids at noon" is NOT part of a Car, but part of a bigger scenario that may contain a car. That scenario would know that the car can drive and turn, etc., but it's naive to how the car actually does it. Sometimes there's the illusion of modularity in software, where a larger behavior is handed off from one thing to the next; but that's not modularity, because you pull one thing out of the pipeline, and you've broken that overall behavior. Generally speaking, you want "vertical" slicing, not horizontal. So instead of A hands off to B hands off to C (which slices what should be one while picture into fragments), you instead have the whole flow exist as one whole thing, and it may call down into A, B, and C. But for example, the code that updates your database should not "know" that it's supposed to do that after the user clicks a button.
πŸ’― 1
One clear indicator of broken modularity is when something tries to "happen at the right time" (rather than just letting the outer environment call into it as needed). This can look like event listeners, AOP, pipelines, or components communicating indirectly with each other through some "mediator" or "command" pattern. Those things can have their place, but if it's to split apart a larger whole, rather than to incorporate multiple unrelated whole things that don't need to work together, than that's not modularity: you're going to have to reverse engineer everything and trace the cause and effect all over the place, to really understand what's happening in that code, and you're opening the door for race conditions and unexpected breakages when things change. The whole point of modularity is to make it easy to understand and change things.
❀️ 1
πŸ’― 2
... I've thought a lot about this before :)
πŸ˜„ 1
k
You can see a program as a hierarchy of modules and scopes, or you can see it as a network of scopes calling other scopes. Which is more important? The hierarchy doesn't feel like the important piece here. On a different note, there's a common idiom of decomposing programs into layers (MVC, app/lib/kernel, etc.) That's arguably a hierarchical organization even though there's no containment. The network is a DAG, and it has a definite 'grain' to it. This feels like an important lens. Arguably here we already have tags, in the form of providers and so on.
n
Some things fit multiple categories, or can play multiple roles, and that does not slice cleanly into exclusive buckets
@Dan Cook Yes, I agree. There's a need to be able to categorise something without allocating it to an exclusive location (a bucket).
Each module [...] may be an inhabitant of a larger world, or may have inhabitants of its own. But it should have no knowledge of the outside world, or of the inside world of its inhabitants.
How do you identity whether a module has inappropriate "knowledge" of something external? You must be talking about Objects specifically, right? Classic information hiding concerns etc. Your answers all seem to be in the context of OOP and method calls.
Generally speaking, you want "vertical" slicing, not horizontal
I'm not really clear what distinction you're trying to make there. Are you arguing for call stacks (RPC) over message-passing and/or events? Call stacks are definitely banned from my own design. They're irredeemable in a distributed system.
you're going to have to reverse engineer everything and trace the cause and effect all over the place, to really understand what's happening in that code, and you're opening the door for race conditions and unexpected breakages when things change
These are problems induced by poor language and tool designs; they're not really an immediate consequence of having an event system. I agree that if you have some kind of event system, then it has to be designed to play a carefully thought-out role in a larger architecture and in a way that can't be abused or "used wrong".
I feel like your overall point is a condemnation of existing software architectures and design patterns, rather than a denouncement (or advocation) of any particular abstract programming primitives. I don't know what else to say to that. I'm certainly not about to defend the status quo of software engineering!
@Kartik Agaram You're talking about code, but what about information? Are you saying that a piece of information (e.g. an employee record, or the state of a video game) should be represented as a network of scopes? How do I address that as a self-contained unit?
Data needs to be modularised too. Not just code!
c
This reminds me of A City Is Not A Tree, where he says a city should be modelled as a semilattice. In particular it has some discussion similar to above about when things are truly "separate" or not
s
The classic paper _The Architecture of Complexity_ has a lot to say about hierarchy, and where it comes from. It's hard to not see hierarchies everywhere, although it isn’t really clear if that's because they are everywhere, or if it's because we just like to see them everywhere. (Hint: it's us. Our brains love containment as a foundational structure for sense-making.)
πŸ‘ 1
w
In the abstract, if making a "module" is taking together N nodes in a graph such that internal connections are isolated from ones that go thru the module boundary, then not allowing hierarchy at all will limit the "arity reduction", since either there will be a ton of modules, or modules will contain a ton of nodes. Hierarchy will help with the cognitive complexity of the graph, IF the average amount of edges is low or very clustered. The problem with hierarchy is what happens when a new edge gets introduced, and the system doesn't allow re-running the modularization heuristics from zero πŸ™‚
d
One thing I'll say it's that it often makes sense to have hierarchy of the data model, and you'll almost always have a hierarchal execution model (e.g. data composition and function composition). But it's a mistake to shoe-horn one into the other. That's the mistake I see most often in OO programs: This part of the use case touches this data (class), so that class owns that slice of the behavior, e.g. Car.DriveToTheStore() (or maybe maybe MapNavigator.DriveToTheStorePart1() calls Car.DriveToThrStorePart2(), etc.)
o
@Dan Cook to illustrate with another example, paper.cut() this for me is a wrong way to model stuff. Somethings are just data and can’t do anything. They should be acted upon instead. It should be cut(paper).
βœ”οΈ 1
k
Multimethods are useful here.
πŸ‘ 1
d
@opeispo - Exactly. I'd say that any proper module are just x, and only contain functionality (if any) for acting on that x. That x can be simple data, or a whole system -- so long as it's a whole system (which can contain other entities that are also whole in the same way). A module does not act on its neighbor or outside world, only on its contents aka "itself". @Nick Smith - That's what I mean by "vertical" slices (e.g. "down" = my constituents) versus "horizontal" (e.g. I'm the middle part, and my neighbors are the left and right parts, of some bigger whole, and none of us can be understood in isolation).
βœ… 1
I saw an ad once, of a boy walking through his house commanding IOT objects to do random things: "Fridge, turn on the kitchen lights. Toaster, read my email. TV, set an alarm on my phone". Or something like that.