Horrendous take. Humans can reason about (some) hardware chips because they are space-constrained. The analogous solution in software would be to limit the size of your programs to a human-readable size—18k loc or so.
The author seems to like black boxes. Unix corelib is a collection of black boxes[1]. So are docker containers[2]. Do you know what else are black boxes? TYPES. Specifically interfaces. Types let you compartmentalize your software into as many black boxes as you want, then compose them.
———
[1] Try building any non trivial service by composing Unix corelib tools, docker containers, or REST APIs. You’re in for a world of hurt.
[2] Docker containers aren’t even really isolated. The JupyterHub DockerSpawner image spins up and tears down other docker containers at will. The whole thing is a mess of state. There’s no composition—just the container equivalent of `goto`
I agree in general with the principle that there is a 'human-readable' size, and that it's around some 10k-30k LOC, but I'm curious how you came up with that specific number. Has it been researched and measured? Does it vary significantly from person to person? I tend to think of 80kLOC as the upper limit, because that's the size of the DOOM engine, written by a single highly-capable programmer, who presumably had it all in his head at one point. But that is empirically way beyond what I'm capable of.
- the explicit interfaces for modules used in hardware design _are_ types.
- hardware design requires extensive testing and verification, and formal verification (using types) is a valuable step in hardware design.
I think the author conflates the use of types with structural complexity - at least at the introduction of his article, hence the term "symptom" used to qualify type checking in the title (actually _lengthy_ type checking, this is implied, but without context the title of the article becomes clickbait-y).
Indeed, by the end, the author admits that types are useful, without conceding that the initial idea was an error of judgment.
Maybe I’m in the minority but I’ve never associated a type system with scale/maintainability directly.
I treat them as I would unit testing: a useful tool to catch bugs early and if you’re skipping it, it should be an intentional decision where you’ve accepted the trade offs.
This is silly. Types are specifications of programs and data. The only reason that isn't obvious is because most of our type systems are rudimentary. It becomes obvious once you pick up anything with a bit more power (Haskell with extensions or any dependently typed programming languages). A type system is not just another ad-hoc solution, it's fundamental and essential to what we're doing. This is obvious if you know the (pre-computer) history of type theory: Types specify sets of objects intensionally. Types describe what our program should do and are also a very clear form of documentation. On top of that, you can't even get rid of types if you wanted. The best you can do is make them implicit. You have to make a decision about how to interpret data in the end, and that's typing. Even assembly language and machine code has types. Even electronics have types, we just don't think of it that way (but look at the 7400-series ICs if you want explicit examples).
This post is written by someone rather naive of computer science. Their description of a "function" betrays that. The failure to recognize UNIX pipelines as function composition also betrays that and so does the whole "black boxes" paragraph.
> electronics engineers routinely design systems with millions of components, intricate timing relationships, and complex interactions between subsystems. Yet they don’t rely on anything analogous to our type checkers.
Disclaimer: not hardware engineer.
However as a junior in college we had several classes using VHDL and later Verilog. They have types. They are used to simulate design before committing it to board.
A type system lets different parts of a program agree on how to interpret a pattern of bits in memory and then enforce that interpretation. I don't think electronic circuits built from discrete components that have immutable physical properties are analogous in the way that the author apparently thinks they are.
> When components are designed as genuine black boxes—where what happens inside stays inside, and communication occurs only through explicit input and output ports—systems naturally become easier to reason about.
"How do you specify the shape of the input and output ports?" shouts the goose as it chases me away.
All of the examples in the article of “simplicity” involve the orchestration of complex platforms that are, for the most part, written using types. Try writing a browser that can pass Acid2 in Ruby and then try to convince anyone that what you’ve done is in any sense better than the Chromium codebase in all its complex glory.
Maybe the truth is that our job as developers is to manage the inherent complexity of the world we live in.
> Why do we need type checking at all? The standard answer is scale. “Small programs don’t need types,” the reasoning goes, “but large programs become unmaintainable without them.”
No it's not.
Type checking is a first line of defense against bugs. That's all.
It's just making sure you're not accidentally passing invalid data or performing an invalid operation.
And since the article gets the premise wrong, its conclusion that reducing complexity will make type checking "largely irrelevant" is therefore also wrong.
> you can reason about each component in isolation
> you can focus on the explicit contracts between components
> you can design systems where problematic interactions are structurally impossible
In other words, exactly what a strong type system gives you?
My impression from doing my own amateurish hardware projects and knowing some real hardware engineers is that they would KILL for something like the type systems we have in software. In fact there are plenty of hardware description language projects that try to strengthen the type system of the HDL to catch errors early.
If you think a Rails test suite is bad, look at a Verilog test suite sometime.
As other people here have said, type systems are merely an automated check that interfaces are used correctly. But to read this more generously, the author is saying that with sufficiently simple interfaces such checking isn't necessary. That's arguably true, though rather limiting (since a direct composition of simple interfaces is not necessarily a simple interface): a system that presents a single call that reads one byte and adds one to it, with wrapping, doesn't need a type because all inputs are valid.
Unfortunately, the examples given are not particularly representative, because they all imply complex interfaces (reading? writing? buffering?) and complex input/output formats: the shell that is doing the piping only cares about ‘bytes’, but the programs themselves have much more complex requirements on the data, which tends to be what happens once you start assigning semantics to your interfaces.
Even with spatial locality limiting the number of inputs and outputs, the ‘interfaces’ that hardware uses are usually specified in multi-hundred-page manuals, and that's mostly due to having to encode the semantics of what it means to put volts on those wires.
I distinctly remeber back in 2017 or so I was using tensorflow to train a CNN.
At the time Python really hadn't gotten its shit together with regards to typing.
Googles Tensorflow documentation was heavily filled with completely useless high level nonsense like this.
from tensorflow.datasets import mnist
from tensorflow.models import alexnet
alexnet.train(mnist)
Or something like this. Useful maybe if you wanna put "wrote a machine learning classifer" on your resume or something. But not useful.
But the biggest pain of all was that I had no clue what I was supposed to put into any of these functions. The documentation was the function name and arguments names. I ended up just opening Python in interactive mode and calling type on various intermediates running through examples and writing it down.
When you want others to actually be able to use your code you NEED to specify in what actually are your inputs in a clear and unambiguous way. This is fundamentally why typing is vital and usedul even in small codebases. Argument names (or even worse just being like *args, **kwargs just doesn't work.
This is why if you open up any half decent Python library now they actually tell you what you need to provide and what to expect out. Without it you cannot reason about what to do.
> The transport layer [of UNIX pipelines] uses extremely simple data—bytes or characters—which allows universal pluggability. Individual components can layer more sophisticated agreements about data meaning on top of this simple substrate.
No, they can't - they can make assumptions and assertions, but not agreements, because they have no way to communicate such agreements between each other.
Remember when the world went insane and wanted JS (not TS) to run literally everywhere?
Now we live in a world where TS, Python and even PHP admitted the mistake of unrestrained types. People who didn’t get the memo are highly questionable, probably facing solo-dev hacker mindset syndrome.
>The standard answer is scale. “Small programs don’t need types,” the reasoning goes, “but large programs become unmaintainable without them.” This sounds reasonable until you realize what we’re actually admitting: that we’ve designed our systems to be inherently incomprehensible to human reasoning. We’ve created architectures so tangled, so dependent on invisible connections and implicit behaviors, that we need automated tools just to verify that our programs won’t crash in obvious ways.
A few thoughts...
1. Some things are complicated. Of course we should elminate needless complexity - but not all complexity is needless.
2. If your system has huge thing 1 connected to huge thing 2, then you have to understand 2 huge things and a little glue. If you instead have a system comprised of small thing 1 connected to small thing 2, ... connected to small thing 100, you now how to understand 100 small things and 99 connections. The nature of these things and connections matter but you can't just keep dividing things to simplify them. John Carmack's email about inlining code springs to mind: http://number-none.com/blow/john_carmack_on_inlined_code.htm...
3. "A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system." - https://en.wikipedia.org/wiki/John_Gall_(author)
I would love to see the author take a complex system where Typescript (for example) is "necessary" and refactor it to JavaScript.
Prove it. Write a very large program in an experimental state chart-based language and prove that doing the same thing in any conventional language would have been impractical or impossible.
> We debate whether Rust’s borrow checker is better than Haskell’s type classes. We argue about whether TypeScript’s structural typing is superior to Java’s nominal typing.
Life is too short to argue about stupid programming language bullshit. If you want to use Rust, do it. If not, don't. The actual code that makes up the computer system as a whole is still 99.99% the same because you are running on an operating system written in C calling a database or other systems also written in C.
The more pedestrian reason that's there's demand for static typing is that when people press '.' they generally want a menu to appear and for it to be complete and accurate.
>> Instead of worrying about how changes in one module might ripple through dozens of dependencies, you can focus on the explicit contracts between components.
Wow, so much wrong here. I dunno whether it's worth picking apart because it's so obviously wrong but... I'll start:
> The standard answer is scale. “Small programs don’t need types,” the reasoning goes, “but large programs become unmaintainable without them.” This sounds reasonable until you realize what we’re actually admitting: that we’ve designed our systems to be inherently incomprehensible to human reasoning. We’ve created architectures so tangled, so dependent on invisible connections and implicit behaviors, that we need automated tools just to verify that our programs won’t crash in obvious ways.
>
> Type checking, in other words, is not a solution to complexity—it’s a confession that we’ve created unnecessary complexity in the first place.
Woah woah, where did unnecessary come from? Yes we have made super complex systems that need automated checking. But they are (mostly) necessarily complex. Are you going to write a 10k line OS kernel? A 10k web browser? A 10k line word processor? Yeah good luck.
> electronics engineers routinely design systems with millions of components, intricate timing relationships, and complex interactions between subsystems. Yet they don’t rely on anything analogous to our type checkers.
I dunno if he's talking about PCBs... but actually having types for wires is a pretty great idea. Seems like Altium kind of has support for this but I don't know how much checking it does: https://www.altium.com/documentation/altium-designer/sch-pcb...
Anyway, aside from the fact that PCB design is a completely different thing, PCB designers accidentally make wrong connections all the time.
> The problem isn’t that software is inherently more complex than hardware.
It is though. Way more complex. Find me a PCB as complex as Chrome or LLVM. Hell, find me a CPU as complex. Even the latest OoO designs don't come close.
> UNIX pipelines
Ha I knew he was going to suggest this.
> yet they require no type checking at the transport layer
"require" sure, I guess. Now go and look up how many quoting options there are in `ls`. And nobody has ever realized that structured piping is better and have written two popular shells (at least) that support it.
Ugh so many misguided words remain, I'll stop there.
I'd love for type checking in ML code. For example, here's an einsum from my code: "bhsc,cst->bhst". I'd love for a static analyzer to be able to tell if I accidentally swapped two of my dimensions and the einsum won't run, or what the output dimensions of an operation are.
75 comments
[ 2.6 ms ] story [ 73.1 ms ] threadThe author seems to like black boxes. Unix corelib is a collection of black boxes[1]. So are docker containers[2]. Do you know what else are black boxes? TYPES. Specifically interfaces. Types let you compartmentalize your software into as many black boxes as you want, then compose them.
———
[1] Try building any non trivial service by composing Unix corelib tools, docker containers, or REST APIs. You’re in for a world of hurt.
[2] Docker containers aren’t even really isolated. The JupyterHub DockerSpawner image spins up and tears down other docker containers at will. The whole thing is a mess of state. There’s no composition—just the container equivalent of `goto`
Indeed. This person doesn't even know what types or functions are.
I agree in general with the principle that there is a 'human-readable' size, and that it's around some 10k-30k LOC, but I'm curious how you came up with that specific number. Has it been researched and measured? Does it vary significantly from person to person? I tend to think of 80kLOC as the upper limit, because that's the size of the DOOM engine, written by a single highly-capable programmer, who presumably had it all in his head at one point. But that is empirically way beyond what I'm capable of.
- the explicit interfaces for modules used in hardware design _are_ types. - hardware design requires extensive testing and verification, and formal verification (using types) is a valuable step in hardware design.
I think the author conflates the use of types with structural complexity - at least at the introduction of his article, hence the term "symptom" used to qualify type checking in the title (actually _lengthy_ type checking, this is implied, but without context the title of the article becomes clickbait-y).
Indeed, by the end, the author admits that types are useful, without conceding that the initial idea was an error of judgment.
I treat them as I would unit testing: a useful tool to catch bugs early and if you’re skipping it, it should be an intentional decision where you’ve accepted the trade offs.
This post is written by someone rather naive of computer science. Their description of a "function" betrays that. The failure to recognize UNIX pipelines as function composition also betrays that and so does the whole "black boxes" paragraph.
Disclaimer: not hardware engineer.
However as a junior in college we had several classes using VHDL and later Verilog. They have types. They are used to simulate design before committing it to board.
I would be surprised people brute force it often.
This is not accepted wisdom at all.
"How do you specify the shape of the input and output ports?" shouts the goose as it chases me away.
If I felt snarky, I'd say "This is your brain on Javascript".
Maybe the truth is that our job as developers is to manage the inherent complexity of the world we live in.
No it's not.
Type checking is a first line of defense against bugs. That's all.
It's just making sure you're not accidentally passing invalid data or performing an invalid operation.
And since the article gets the premise wrong, its conclusion that reducing complexity will make type checking "largely irrelevant" is therefore also wrong.
> you can focus on the explicit contracts between components
> you can design systems where problematic interactions are structurally impossible
In other words, exactly what a strong type system gives you?
My impression from doing my own amateurish hardware projects and knowing some real hardware engineers is that they would KILL for something like the type systems we have in software. In fact there are plenty of hardware description language projects that try to strengthen the type system of the HDL to catch errors early.
If you think a Rails test suite is bad, look at a Verilog test suite sometime.
"You don't need x, real programmers use y!"
Unfortunately, the examples given are not particularly representative, because they all imply complex interfaces (reading? writing? buffering?) and complex input/output formats: the shell that is doing the piping only cares about ‘bytes’, but the programs themselves have much more complex requirements on the data, which tends to be what happens once you start assigning semantics to your interfaces.
Even with spatial locality limiting the number of inputs and outputs, the ‘interfaces’ that hardware uses are usually specified in multi-hundred-page manuals, and that's mostly due to having to encode the semantics of what it means to put volts on those wires.
I distinctly remeber back in 2017 or so I was using tensorflow to train a CNN.
At the time Python really hadn't gotten its shit together with regards to typing.
Googles Tensorflow documentation was heavily filled with completely useless high level nonsense like this.
Or something like this. Useful maybe if you wanna put "wrote a machine learning classifer" on your resume or something. But not useful.But the biggest pain of all was that I had no clue what I was supposed to put into any of these functions. The documentation was the function name and arguments names. I ended up just opening Python in interactive mode and calling type on various intermediates running through examples and writing it down.
When you want others to actually be able to use your code you NEED to specify in what actually are your inputs in a clear and unambiguous way. This is fundamentally why typing is vital and usedul even in small codebases. Argument names (or even worse just being like *args, **kwargs just doesn't work.
This is why if you open up any half decent Python library now they actually tell you what you need to provide and what to expect out. Without it you cannot reason about what to do.
No, they can't - they can make assumptions and assertions, but not agreements, because they have no way to communicate such agreements between each other.
Remember when the world went insane and wanted JS (not TS) to run literally everywhere?
Now we live in a world where TS, Python and even PHP admitted the mistake of unrestrained types. People who didn’t get the memo are highly questionable, probably facing solo-dev hacker mindset syndrome.
I am not sorry for having strong opinions.
A few thoughts...
1. Some things are complicated. Of course we should elminate needless complexity - but not all complexity is needless.
2. If your system has huge thing 1 connected to huge thing 2, then you have to understand 2 huge things and a little glue. If you instead have a system comprised of small thing 1 connected to small thing 2, ... connected to small thing 100, you now how to understand 100 small things and 99 connections. The nature of these things and connections matter but you can't just keep dividing things to simplify them. John Carmack's email about inlining code springs to mind: http://number-none.com/blow/john_carmack_on_inlined_code.htm...
3. "A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system." - https://en.wikipedia.org/wiki/John_Gall_(author)
I would love to see the author take a complex system where Typescript (for example) is "necessary" and refactor it to JavaScript.
> We debate whether Rust’s borrow checker is better than Haskell’s type classes. We argue about whether TypeScript’s structural typing is superior to Java’s nominal typing.
Life is too short to argue about stupid programming language bullshit. If you want to use Rust, do it. If not, don't. The actual code that makes up the computer system as a whole is still 99.99% the same because you are running on an operating system written in C calling a database or other systems also written in C.
So, typing.
> The standard answer is scale. “Small programs don’t need types,” the reasoning goes, “but large programs become unmaintainable without them.” This sounds reasonable until you realize what we’re actually admitting: that we’ve designed our systems to be inherently incomprehensible to human reasoning. We’ve created architectures so tangled, so dependent on invisible connections and implicit behaviors, that we need automated tools just to verify that our programs won’t crash in obvious ways. > > Type checking, in other words, is not a solution to complexity—it’s a confession that we’ve created unnecessary complexity in the first place.
Woah woah, where did unnecessary come from? Yes we have made super complex systems that need automated checking. But they are (mostly) necessarily complex. Are you going to write a 10k line OS kernel? A 10k web browser? A 10k line word processor? Yeah good luck.
> electronics engineers routinely design systems with millions of components, intricate timing relationships, and complex interactions between subsystems. Yet they don’t rely on anything analogous to our type checkers.
I dunno if he's talking about PCBs... but actually having types for wires is a pretty great idea. Seems like Altium kind of has support for this but I don't know how much checking it does: https://www.altium.com/documentation/altium-designer/sch-pcb...
Anyway, aside from the fact that PCB design is a completely different thing, PCB designers accidentally make wrong connections all the time.
> The problem isn’t that software is inherently more complex than hardware.
It is though. Way more complex. Find me a PCB as complex as Chrome or LLVM. Hell, find me a CPU as complex. Even the latest OoO designs don't come close.
> UNIX pipelines
Ha I knew he was going to suggest this.
> yet they require no type checking at the transport layer
"require" sure, I guess. Now go and look up how many quoting options there are in `ls`. And nobody has ever realized that structured piping is better and have written two popular shells (at least) that support it.
Ugh so many misguided words remain, I'll stop there.
Which is exactly what you find a ton of in electrical engineering (e.g. IEEE C37.2 and gazillion more).
I'd be forever ashamed if I wrote that.