Flexible, Organized Knowledge
Table of Contents
What are we trying to get from our thinking and learning? Flexible knowledge, not fragile knowledge; knowledge we can apply to new cases instead of only to the cases we were taught to apply it to. We want to understand things ourselves so we can think for ourselves, not accept assertions on authority.
The goal of knowledge is to help you understand things. To get conceptual understanding. To know principles. To be able to explain how and why. To understand causes. To connect many different ideas together and understand their relationships. This lets you apply your knowledge to anything it applies to instead of only to what you were told to apply it to. It lets you think for yourself and think new thoughts.
In the standard view of the world, basically all our ideas are refuted, except a few special, simple cases like 2+2=4. Those cases stand out so much that people confuse them with infallibility and talk about proof and certainty. Actually, we should have lots of knowledge with high confidence. We should categorize many other ideas as non-refuted instead of accepting that basically all our thinking is flawed. It’s possible to actually get ideas that don’t have known flaws.
By the way, most people impressed by arithmetic and logic arguments know rather little about them. E.g. they don’t know how an adder can be constructed from simpler circuits/gates in a CPU. They view addition as indivisible – a basic operation which cannot be analyzed into smaller parts. That is false and the simpler parts that make up addition are well known to experts. Multiplication is even more complicated and experts like Donald Knuth actually publish different multiplication algorithms with meaningful speed differences. Addition has complications too, such as dealing with different number formats like twos complement or floating point. Addition also has issues for how to handle negative numbers and large numbers that don’t fit in limited memory.
Errors and Refutations
An important goal is to have an overall understanding of what categories of ideas are refuted, by what arguments, and how to design ideas which are not refuted. You should be able to stand up for some ideas, and defend them against all questions and criticism, instead of having to say “Well, your standards are so high that I’d have to give up on knowing anything if I had standards that high.” You can know things to the standard that there are no known criticisms.
A criticism is an explanation of why an idea won’t work – won’t accomplish the goal it’s intended to accomplish – so it’s not ignorable. On the other hand, people sometimes argue using non-criticisms, which you can counter-argue by pointing out “Even if that is true, it would not prevent this idea from working for its purpose.” E.g. if someone is complaining about a typo which won’t confuse a reader, then it’s not a criticism because it doesn’t prevent the text from working. E.g. if you text your spouse “I will get milk from Walmrat on my way home.” that typo is not a refutation because your spouse will know what you mean, the text will work, it will be successful.
When you have non-refuted ideas, a single error is a big deal. One error can change something from non-refuted to refuted. One error can be the difference between something working or not working. If you’re already putting up with broken ideas that don’t work, then finding out about an error may not make much difference. If you accept refuted ideas as the status quo, errors don’t make much difference because you already have errors anyway.
When you find out about an error, consider how you can fix the idea so it no longer has an error. A small change might fix it without breaking anything else. But it might not. You might only come up with a major change to fix this, or find no fix at all and need an alternative idea (a different approach).
APIs, Coupling and Independent Parts
We want knowledge to be easy to get and easy to change. This requires structuring it as many small parts with connections, with each part being approximately autonomous (independent), so any small part can be changed by itself without disrupting other parts. We don’t achieve this perfectly but we can approximate it so that changing one part usually only affects a small number of other parts. This is called loose coupling by software developers.
Loose coupling means the connections between parts are “loose”, not “tight”, meaning that you can change one without affecting the other. A tight connection/coupling means the parts are so intertwined that they are, to a meaningful extent, not actually separate parts.
In software, the standard way to get independence between parts is by APIs which are structured interfaces that govern how parts interact. A standard organizational principle is that the parts are not allowed to interact outside of the APIs. When all interactions between parts are organized and kept to some limited types of interactions, it’s much easier to keep code independent. Coders can know they don’t have to worry about their code being interacted with except in specific ways that they can plan for.
Suppose you’re writing software for a vending machine with soda cans. You could have an overall management part, a money-taking part, an order-taking part and a drink-dispensing part. The money-taking and order-taking parts notify the overall management part when their jobs are done. The overall management part then sends a command to the drink-dispensing part to give the appropriate drink. The money-taking and order-taking parts do not interact with each other nor with the drink-dispensing part. And the management part doesn’t know anything about how a drink is dispensed or what types of coins exist. It only knows a list of valid commands like “dispense X drink” and “take Y dollars” – those are part the interface of allowed commands.
The management code knows nothing about how a command is accomplished. If there is a bug in the command for dispensing drink, it could be fixed without ever telling the programmers who write the management code. The dispensing code could be entirely rewritten and, as long as it keeps the same interface (accepts the same list of commands), then the management code could be left unchanged and wouldn’t care at all about the details of how the dispensing works. That’s what loose coupling and independence are like.
The same management code could even be used with a different machine with different motors, and which takes credit cards instead of physical money. It shouldn’t matter as long as the same interfaces with other code libraries work.
Your knowledge should be like this – independent parts with loose coupling using structured, organized interfaces instead of a chaotic mess of connections between ideas. Ideas should interact by specific connections so that if you change an idea, as long as the same connections work, then no other ideas are affected. You should know every connection.
Bad code means you change something in one part and something somewhere else breaks because it was interacting with the details of another part in some way you didn’t realize. That means there was an interaction between the two pieces of code that wasn’t using the official interface – the API.
The meaning of separation of parts (called “separation of concerns” too) is that things are actually separate/independent – changing the internals of one part has no effect on the internals of any other part. Ideas should have internal and external parts, and changing the internals of one idea should have no relevance to the internals of another idea. You should be able to actually know which parts of ideas are the internals – the parts that no other ideas interact with directly – and which parts are the external interface that other ideas can make connections to.
(It’s more complicated than this because if you examine an idea in detail, and think about its internals, then you’re making connections to those internals, because thinking involves ideas. But basically you can connect your long term important ideas via APIs, and connect ideas to internals in a temporary way.)
Besides loose coupling, a key thing to aim for with your ideas is small parts. That helps ideas be flexible and easy to change. If ideas are big then there are many different internal parts interacting in complex ways. That makes it hard to change anything without breaking something. It takes a lot of analysis to understand what’s going on. Having a big idea with complex internals is actually similar to having multiple ideas with interactions between their internals. The point of APIs is to manage and separate complexity. You need components that are small enough (low enough complexity) to get right, and then you need to limit how they’re connected together to keep complexity down.
So, make every idea small and simple, and carefully control what information it shares externally. Then, all the internal parts can be adjusted while only worrying about that one small idea, and no other ideas, so you have little complexity active in your mind. You only have to worry about multiple ideas at once when connecting ideas or when changing APIs, but not when internal stuff changes. (APIs should be designed thoughtfully. You can’t expect perfection, but try to make them reasonably flexible and future proof. You want to limit how often you have to change APIs.)
When you change APIs, the directly connected ideas are affected. In other words, other ideas/code that use that API have to change how they use it. The change is often pretty easy to make. For example, the old API might be dispense-drink(type), meaning you specify a drink type when calling the dispense-drink API. A change to the API could change the list of valid drink types or it could add a new variable to specify drink count. The API could be updated so instead of always dispensing one drink at a time, it could dispense multiple drinks of the same type when a number like 4 is specified. A more complicated API could allow dispensing multiple drinks of multiple different types with one API call.
Updating code that uses an API, because the API changed, is usually pretty easy because, generally, there’s still a way to use the API to accomplish the same thing as before. So it’s a superficial change because you have to say “Do X” using different words/code, but you still call the API to accomplish X.
But sometimes an API change is “big” and requires big changes to other connected ideas. For example, we might put our management code in a vending machine that sells food not drinks. Then, where we used to call the dispense-drink API, we’ll need to instead use a dispense-food or dispense-item API. If we put our code in an automated factory where materials have to be released periodically, we’d also need significant changes. Instead of dispensing stuff in response to customers, we’d need to dispense stuff on a schedule.
Big changes can cause chain reactions where lots of ideas or code have to be reconsidered or edited. Chain reactions are hard to deal with and we should try to avoid them. They mean there’s a lot more work to do. They can always happen, but they happen way less if your ideas are organized (structured) well. Chain reactions spread over tight coupling better than over loose coupling. Chain reactions spread more when there are more dependencies, and spread worse when there’s more independence/autonomy.
To avoid big changes, it also helps to take into account a wide variety of criticism while developing ideas (or code). That way, the ideas aren’t full of known errors, so the pace of finding out new errors will usually be reasonably slow. The rate of innovation is more reliably slow than the rate of bothering to check what’s already known, which can sometimes have a big spike (when you find one error that was already known, you’ll look around for others and may find a bunch). Although a single error can lead to big changes, multiple errors are more likely to require big changes to address.
If your ideas are in big chunks that aren’t independent and have undocumented connections between each other, chain reactions affect more stuff and are harder to understand. Some effects may not even be noticed/found. Using small, independent ideas manages complexity better and has a comparatively easier time dealing with problems. It’s broadly better rather than being a tradeoff or compromise.
People have trouble knowing what their ideas are or how their ideas are organized. They have poor control over these things. So how can they improve? Find some fairly simple, easy area (e.g. grammar, speedrunning, arithmetic) and create organized knowledge in that area. And make your knowledge more explicit using tree diagrams, notes, outlines, written explanations, etc. Practice and get good at stuff. Figure a topic out and get a bunch of actually-non-refuted ideas about it that you have confidence in. Learn to understand how they connect to each other and try to understand what can be separated from what. If you do this well with something, you’ll repeatedly go through the experience of revising what you know. You’ll have to change your mind about stuff and then go fix all the affected ideas. And when you fix those ideas, those changes could necessitate changing ideas they connect to. And then those changes could require changes to connected ideas. You’ll have to manage and contain the chain reaction, which will help you see what is connected to what. You’ll get better at dealing with such things, planning ahead better so it’s less of a problem next time, etc. The point is, get high quality explicit, organized knowledge of something and that’ll give you a mental model of what it’s like and give you experience dealing with it. That’ll help you when dealing with your existing ideas. Alternatively, you could learn programming, both as something to get good at and because programming is about organizing knowledge. Programming is a cousin of epistemology.