Understanding the problem is more important
A huge obstacle in software development is understanding the thing in the first place. In fact, I'd argue it's one of the biggest obstacles. I suspect this is a leading reason why static typing is advocated for. But I want to put the focus back on understanding, because that takes precedence over tech stacks, langauges, and type systems.
In my experience, there are two layers to undertanding software:
- Conceptual Model: Why it works. Great architecture.
- Implementation Model: How it works. Great code.
The most important part of any project is always conceptual. It's also the hardest to get right. Why do we need this project in the first place? What are the key architectural components?
The implementation is the easy part. It takes time, but it's easy (comparatively).
E.g The Linux Kernel:
- Conceptual Model: It's a monolithic kernel. Userspace programs interact with a kernel API, the kernel interacts with hardware. We hear mantras such as "everything is a file" and "kernel updates never break userspace".
- Implementation Model: PID, file system, C structs, stable ABI.
Types aren't that great for documentation. Examples are!
Try reading auto-generated commentless JavaDocs someday, or a man-page without examples. It lists what's there - the arguments, flags, methods, etc...
… but what does that mean semantically? Why would I call this? How am I supposed to call this?
The biggest advocates for static type systems will insist that types are self-documenting. I humbly and strongly disagree. In fact, some of the worst documentation I've seen have come from such projects, and I suspect it comes from a delusion that you don't need to document anymore.
Very Busy Developers will insist that JavaDocs are documentation. When developers lean on auto-generated docs to close the Documentation task on their scrum board, it discourages cross-sectional documentation by example.
I learn best by example - and I know I'm not alone. After all, all abstractions are built on top of concretions (examples).
Let's take Ramda's R.path
documentation without the example:
path
[Idx] → {a} → a | Undefined
Idx = String | Int
Can you guess what it does?
The Ramda docs use a Haskell notation. I'll also rewrite it to a more familiar TypeScript-style notation:
path
(l: array of Idx, m: map of (Idx,A)) -> A or undefined
We know that path is a function that takes two arguments, and returns the generic type A (or undefined).
Here's the example:
R.path(['a', 'b'], { a: { b: 2 } }); // returns: 2
R.path(['a', 'b'], { c: { b: 2 } }); // returns: undefined
You can probably figure out what this does by looking at the examples. If you do have additional questions about how this works, you can always fire up a Node.js REPL and try it out yourself.
In conclusion, a plea: Add more examples!
I'm begging documentation writers everywhere to understand: It's not enough to just tell us what's there. You need to tell us when/why to use it, and how to use it in a practical sense.
- What do typical inputs look like?
- What does the result look like?
- When would I want to use this?
- When wouldn't I want to use this? (list alternatives)