1. 40
  1.  

  2. 19

    I’ve started to appreciate this perspective recently. It’s easy to get carried away with always digging deeper to learn the next lowest level. This inevitably leads to finding new, ugly problems with each new level. If you’re anything like me, you’ll constantly feel the urge to rewrite the whole world. It’s not good enough until I develop my own CPU/OS/programming language/UI toolkit/computer environment thing. This is also the problem with learning things like Lisp and Haskell (which are “low-level” in a theoretical sense).

    At some point, you have to accept that everything is flawed, and if you’re going to build something that real people will use, you have to pick a stack, accept the problems, and start coding. Perfect is the enemy of good after all.

    But there is still value in learning low-level languages, and the author may have gone too far in his criticisms. In high school, I learned C and decided the whole world needed to be rewritten in C. I wrote parts of games, kernels, compilers, and interpreters, and learned a lot. My projects could have been more focused. I could have chosen more “pragmatic” languages, and maybe built software that actually got used by a real end-user. Still, there were a few lessons I learned.

    First, C taught me how little library support you actually need to build usable software. To make an extreme comparison, this is totally at odds with how most JavaScript developers work. Most of my C projects required nothing but the stdlib, and maybe a library for drawing stuff to the screen. Sure, this meant I ended up writing a lot of utility functions myself, but it can be pretty freeing to realize how few lines of code you need to build highly interactive and useful applications. C tends to dissuade crazy abstractions (and thus external libraries) because of the limits and lack of safety in the language. This forces you to write everything yourself and to understand more of the system than you would have had you used an external library.

    The corollary is recognizing how difficult things were in “the old days” when components were less composable and unsafe languages abounded. We have it good. Sure, software still sucks, and things are worse now in certain ways because of multithreading and distributed systems, but at least writing code that does crazy string manipulation is easy now[1]

    The other value of learning low-level programming is that it does come in handy 1% of the time when the abstraction leaks and something breaks at a lower level. In such a situation, rather than being petrified and reverting to jiggling the system and hoping it’ll start working again, you roll up your sleeves, crack out gdb and get to work debugging the segfault that’s happening in the JVM because somebody probably missed an edge case in the FFI call to an external library. It’s handy to be able to do this[2].

    I’ll continue to use the shorthand of “knowing your computer all the way to the bottom” as meaning understanding to the C/ASM level, but I’ve definitely become more cognizant of the problems with C and focusing too much on optimization. I love optimization and low-level coding, but most problems will suffer from the extra complexity of writing the whole system in C/C++. Code maintainability and simplicity are more important.

    [1] Converting almost any old system into a modern language would massively reduce the total SLOC and simplify it considerably. The problems we have now are largely the fault of us cranking up the overall complexity. Some of the complexity is incidental, but most is accidental.

    [2] As a bonus, you look like a total wizard to everybody else too :)

    1. 13

      The other value of learning low-level programming is that it does come in handy 1% of the time when the abstraction leaks and something breaks at a lower level. In such a situation, rather than being petrified and reverting to jiggling the system and hoping it’ll start working again, you roll up your sleeves, crack out gdb and get to work debugging the segfault that’s happening in the JVM because somebody probably missed an edge case in the FFI call to an external library. It’s handy to be able to do this[2].

      I guess my perspective on this is a bit warped from leading SRE and Performance Engineering type teams for so long. This 1% is our 80%. So it’s often about looking through the abstractions and understanding the underneath of how something is failing or inefficient. In today’s cloud world, this can directly translates into real dollars that dynamically fluctuate depending on the efficiency and usage of the software we’re running.

      It seems like most of the perspectives here, and in the main article are in the context of writing application code, or business logic in situations that are not performance critical.

      1. 2

        Yeah, I generally consider stuff like ops and SRE to be at the OS/kernel level anyway. I’d guess you generally are less concerned about business logic, and more concerned with common request behaviors and how they impact performance. But I think (to the original author’s point), that understanding how filesystems perform or how the networking stack operates in different conditions is essential for this type of work anyway. SRE’s are actually a group that would have a real reason for experimenting with different process scheduling algorithms! :P

        Digging lower-level for an SRE would probably include doing things like learning how the kernel driver or firmware for an SSD runs or even how the hardware works, which probably has less of a return in value than getting a broader understanding of different kernel facilities.

      2. 3

        how few lines of code you need to build highly interactive and useful applications

        Sure, if you ignore necessary complexities like internationalization and accessibility. Remember Text Editing Hates You Too from last week? The days when an acceptable text input routine could fit alongside a BASIC interpreter in a 12K ROM (a reference to my own Apple II roots) are long gone. The same applies to other UI components.

        1. 2

          s/accidental/essential

          1. 0

            C taught me how little library support you actually need to build usable software. To make an extreme comparison, this is totally at odds with how most JavaScript developers work. Most of my C projects required nothing but the stdlib,

            Most people who make this claim about JavaScript don’t appreciate that there is no stdlib. It’s just the language - very basic until recently, and still pretty basic - which amounts to a couple of data structures and a hodge podge of helper functions. The closest thing there has been to a stdlib is probably the third party library lodash. Even that’s just some extra helper functions. People didn’t spend countless hours implementing or using a bunch of libraries out of ignorance, they did it because there was no stdlib!

            1. 6

              Most people who make this claim about JavaScript don’t appreciate that there is no stdlib

              Um. Depending on the minimum supported browser, JavaScript has at least string manipulation methods (including regex, splitting, joining, etc), garbage collection, Hash tables, sets, promises, BigNums, UTF8, exception handling facilities, generators, prototypical OO functions, and DOM manipulation functions. Every web browser supports drawing things with (at least one of) the canvas API, SVG, with CSS styling. You get an entire UI toolkit for free.

              C has precisely zero of those. You want hash tables? You have to implement your own hashing function and built out a hash table data structure from there. How about an object-oriented system? You’ll have to define a convention and implement the whole system from scratch (including things like class hierarchy management and vtable indirection if you want polymorphic behavior).

              In JavaScript, a “string” is a class with built-in methods containing a bounded array of characters. In C, a “string” is a series of contingious bytes terminated by a zero. There’s no concept of bounded arrays in C. Everything is pointers and pointer offsets. You want arrays with bounds checks? Gotta implement those. Linked lists? Build ’em from the recursive definition.

              Literally the most string manipulation-ish behavior I can think of off the top of my head is the strtok function defined in string.h. It performs string tokenization by looping through a string until it hits a space then moves a pointer to the beginning of the word and inserts a null terminator at the end of the word, keeping track of what character was there before in global memory. It does this to avoid an allocation since memory management is done manually in C. Clearly it’s not threadsafe.

              That’s about the highest-level thing string.h can do. It also implements things like, oh, memcpy which is literally a function that moves raw bytes of memory around.

              Maybe JavaScript’s stdlib isn’t as extensive as, say Python’s, but it exists, and it is not small (and to my original point, is far nicer for getting real work done more quickly than what was possible in the past). But every external library that’s included in a JS application gets sent to every client every time they request the page[1]. I’m not saying that external libraries should never be used with JS, but there’s a multiplicative cost with JS that ought to encourage more compact code than what is found on most websites.

              [1] Yes, sans caching, but centralizing every JS file to a CDN has its own set of issues.

              1. 3

                What is a stdlib if not a library of datatypes and functions that comes standard with an implementation of a language?

                1. 2

                  What do you call String and RegExp and Math, etc in Javascript? These are defined by the language spec but basically comparable to C’s stdlib.

                  And of course, in the most likely place to use Javascript, in the browser, you have a pretty extensive “DOM” api too (it does more than just dom though!).

              2. 7

                Down under the level of assembly language is the level of hardware design diagrams. And under those are transistors. And down under that is the level of magnetic currents and fluctuations. And if you go down much further we don’t have language for the levels and the problems any more — my understanding bottoms out even before “quantum physics” — but even lower levels still exist.

                I like the article but this bugged me. It’s largely irrelevant what’s below that which can be achieved with assembly language, because that isn’t programming. You can’t change the hardware design with code (FPGAs aside…) so it’s not relevant to the discussion.

                1. 8

                  It really depends on the context. If you’re a programmer for an embedded system, selecting an appropriate MCU for the task is arguably a fundamental responsibility of the job depending on how your company is organized.

                  If you’re locked into the x86 world, you can report issues to AMD and Intel and it’s frequently possible for a fix to be delivered in a microcode patch.

                  Edit: Also, to the fundamental argument of “hardware isn’t programming so it’s not important,” you should understand, at a minimum, one layer below the lowest layer that you work on. Otherwise, how will you know if your layer or the layer below it is misbehaving when you’re debugging a problem?

                  1. 4

                    I concur that understanding layer N-1 is a good idea. That, and having a deep understanding of what can go the application code. It’s very helpful to understand what sorts of “magic” can happen, and how to de-mystify them.

                  2. 4

                    I like the article but this bugged me. It’s largely irrelevant what’s below that which can be achieved with assembly language, because that isn’t programming. You can’t change the hardware design with code (FPGAs aside…) so it’s not relevant to the discussion.

                    That can still be relevant: how CPU caches behave is super relevant for performance, but you can’t change how they behave. And even beyond that, Rowhammer showed us that even very low-level details like how closely chips are packed can be influenced all the way up from high level languages, even JavaScript. Even something as super-low-level like cosmic rays striking your RAM causing bits to flip might even be something that could crop up from time to time (and you could error-detect/correct it in software), especially if you’re writing code to run on spacecraft.

                    1. 2

                      Sure, but they’re not really relevant to the points that are “You should learn C”, “You should learn asm”, “You should write an OS” etc. There’s no “You should build an x86 machine from scratch”, because it’s not about programming, that’s electronic engineering. I thought about cache optimisation as part of that which can be achieved with assembly, which is relevant; hardware design diagrams are, for 99.99% of cases, not.

                      1. 4

                        Our main technical university teaches all those things. In my country you can’t become a (formally educated) web dev without learning about electronic engineering (it’s in the name even).

                        1. 1

                          It sounds like your country is doing it wrong.

                          1. 1

                            Sounds right to me

                            1. 1

                              Well the school isn’t for web developers, it’s for engineers and computer scientists. However it just so happens that a lot of them become web developers because that’s relatively lucrative and a popular avenue at the moment.

                              The school does some things wrong but overall it’s a great, intensive program.

                            2. 1

                              What university is that?

                              1. 2

                                https://www.fer.unizg.hr/en

                                I should mention I was wrong, there are some more vocational schools here where you indeed don’t learn about most of that stuff, they just teach you to code in something practical.

                        2. 3

                          Realistically, you can’t change the kernel or the web browser either.

                          In a sense, it is useful to define software as something you can change, reclassifying the kernel and the web browser as part of hardware. Depending on your situation, Django or Rails also can be the hardware. While it is sometimes useful to know about the hardware, I agree it is mostly irrelevant for people working on software.

                          1. 7

                            You can realistically change the kernel and/or the browser, or more realistically write patches and improvements for the existing code. That’s not a realistic option with your processor.

                            1. 0

                              No, you can’t. Maybe you are submitting your application to App Store, maybe your web app needs to run on iOS Safari. I guess you could apply to Apple, but then, you could also apply to Intel.

                              1. 7

                                Have you never compiled your own kernel or run Greasemonkey?

                                You very much can change both the browser and kernel. A locked-down platform means the platform is wrong, not the person attempting to modify it.

                                1. 1

                                  If you feel like it, read iOS instead of the kernel and Safari instead of the web browser. My point still stands.

                                  iOS and Safari are as unmodifiable as Intel CPU for me. That one is hardware and the other is software matters little.

                                  1. 3

                                    Using a CPU you can’t modify is unavoidable for anyone using a computer. Avoiding using iOS or Safari is very easy in comparison.

                                    1. 10

                                      You two are talking past each other: one of you is speaking corporate engineer at work and the other is speaking FOSS zealot. Both perspectives are valid, but neither is complete.

                                2. 3

                                  All kinds of apps and updates get into the App Store. It’s a multi-billion dollar business generated by 3rd parties. Whereas, Intel’s silicon is totally made by them with no 3rd-party patches. Your comparison might work for microcode. They seem to make those changes on their own. Sparingly, too.

                                  One can definitely get a valuable update into a web browser or onto Apple phone before making a change to pre-deployment or post-deployment silicon. Software shouldn’t be thought of like hardware unless it’s software nobody can change or understand. Closest comparisons are mission-critical, legacy systems in enterprises and governments.

                            2. 2

                              But there are machines, like the PERQ where you can change the machine code.

                            3. 6

                              The article linked within, C Is Not a Low-Level Language, was a very interesting read.

                              1. 3

                                As Carl Sagan said: “To make an apple pie from scratch, first you have to create the Universe.”

                                1. 3

                                  I think learning c (/assembly/whatever) is important for reasons other than the post claims people say. It gets you ‘to the bottom’ not physically, but conceptually. In mathematics, there’s an idea of derivation from first principles: you establish your axioms and undefined terms, and from there derive the entire universe. Most programming languages are similarly derived from c and can be expressed as c. Even functional languages, which tend to be conceptually much closer to the lambda calculator than the turing machine (which is c’s progeny), generally still leak details around the edges in ‘practical’ implementations (think ghc vs original haskell).

                                  I completely agree regarding re c not aligning with the actual performance needs of the processor (j and k, which run on dumb interpreters, are faster). But I still think that c itself, as a language is important to know. It’s still closer to what all the layers above the cpu think in terms of. And even if you do want to do low-level squeeze-out-every-last-cycle type optimization, you have to know c. Because the cpu isa was designed to be analogous to c, and only after that to allow you to pass through extra information along side channels; it starts with c.

                                  1. 3

                                    This is so true. As a rule, skills don’t transfer. I am more of a compiler geek than an OS geek, but I openly say to anybody who would listen: learn the compiler if and only if you want to learn the compiler. Do not expect learning the compiler to improve your skills in any other kinds of programming. It is not privileged.

                                    1. 11

                                      Slight counter point. I’ve watched the best software engineer on my team explain to others what the Go compiler is doing with a particular piece of code that makes it behave and perform in a certain way. This kind of knowledge and explanation led to a better implementation for us. I’m not sure he’d be able to offer the solutions he does without some knowledge of what the compiler is doing. This is in the context of code in a messaging pipeline that has to be highly reliable and highly efficient. Inefficient implementations can make it so our product is just not economically feasible.

                                      1. 2

                                        When I see a piece of code, I often try to reason about how it has to be implemented in the compiler and what it has to do at runtime to understand its properties. Going into the compiler is useful this way. For example, if I want to know how big I can expect a Java class with a super class to be, I know that it can’t do cross-class-hierarchy layout optimization. I know this because having the layout change based on the subclass would make virtual calls tricky to implement.

                                      2. 3

                                        Good example. My main takeaways from hacking compilers were parsing and pipelining. Parsing should still be automated where possible with parser generators. That said, seeing the effect clean vs messy grammars had was enlightening in a way that had me eyeballing that in future decisions about what data formats to use. LANGSEC showed up later doing an even better job of illustrating that with Chomsky’s model. Plus, showing showing how damaging bad grammars can be.

                                        Pipelining was abstract concept that showed up again in parallization, UNIX pipes, and services. It was useful. Don’t need to study compilers to learn it, though. Like in OP, there’s already a Wikipedia article to give them instead.

                                        1. 3

                                          Grammars and parsing are orthogonal to compilers. Everyone who ever deals with file formats must learn about that because people who don’t know tend to produce horrible grammars and even worse parsers that only work correctly for a tiny subset of cases.

                                          Still, one can learn it without ever hearing about the things compilers do with ASTs.

                                          1. 1

                                            I agree. Compiler books and articles were just my only exposure to them at the time. Modern Web having articles on about everything means it’s easier than ever to narrow things down to just the right topic.

                                            1. 2

                                              Yeah, and then a number of compiler books don’t really discuss the implications of grammar design either, but get to subjects irrelevant for most readers right away. Like, nobody is really going to write their own code generator anymore now that all mainstream compilers are modules (and if they have a good reason to, they are not or should not be reading introductory books).

                                        2. 3

                                          There is one skillset I think that learning a compiler would help you with: How to approach a large and foreign codebase. You don’t have to learn a compiler to practice it, but I think both learning how to approach unknown codebases is a cross-cutting skill.

                                          Of course, you can also learn that skill by reading the source to your web/GUI/Game/App framework of choice, assuming it’s available. I do think the general idea of building something that you usually only ever consume is a good notion.

                                          1. 2

                                            I feel like this article starts out by arguing against a straw man. How many people actually believe, and would give, the ‘advice the author things they are supposed to give’?

                                            It then generalizes that advice to “that you should learn about computers and software past all the abstractions, ‘all the way to the bottom.’”, but the three bullets summarizing the thing they were ‘supposed to tell you’ don’t sum to that at all. The article itself explains that there’s more to going ‘all the way’.

                                            It then goes on to double down on that, getting to the title. Which is subsequently doesn’t argue at all. What is does argue is that it’s neither useful nor necessary to know your computer all the way to the bottom, with sides of “how people advise you to get there doesn’t actually work” and “you can’t understand everything to the same extent you understand your areas of expertise”. But not that “there’s no such thing”.

                                            Although the article says many true and useful things, I disagree with all these things it argues and vehemently with the condensed version to which it doesn’t even generalize.

                                            To start with the last point: understanding things superficially, and knowing that is the case, is still better than not understanding them at all. Usually it means you at least know where and how to get more knowledge and how a level ties into its surround levels. Understanding everything at the same level of expertise is ludicrous. That doesn’t mean the alternative is ‘no knowledge at all’.

                                            Then the other aside: that the common advice doesn’t work doesn’t mean there aren’t ways of learning things that do work. Especially if the goal is only to get enough basic understanding of the level to understand how levels work together. That also means that learning ‘the best’ or ‘most accurate’ thing is necessary to broadly achieve a goal. I believe having learned some C has been helpful and useful to me, even though I’ve never used it.

                                            And to get to the main argument: that something isn’t useful or necessary for our work or hobbies is just naively utilitarian. Firstly, many people just want to know stuff works, even if it is irrelevant. Is it useful or necessary to know how a rainbow gets formed? In addition, many things that seem useless turn out to be useful at unexpected moments, not because knowledge the knowledge itself is applicable, but because a general principle or insight is useful.

                                            So yes, it’s not necessary to know your computer ‘all the way to the bottom’ to be a good programmer. The article gives good reasons why and rightly warns against mistakes in advice you may receive as to this necessity and how to achieve it. But learning how a computer works ‘all the way to the bottom’ is eminently possible, both in total scope and scope at each ‘level’, not less useful that many other kinds of knowledge you could acquire instead in the time you spend on it and possibly very satisfying.

                                            1. 2

                                              At root, this gets at whether you’re a technician, an engineer, or a scientist, along with the level of play one brings to the arena. The author is arguing primarily for the technician/no-play perspective; a high focus on utilitarianism and some deprecation of general learning.

                                              They are not wrong with how misleading C, assembly, and the simplified systems can be though. The author is, I think, targeting early-career people, who don’t have the context to understand the deeper why of the systems they are interacting with.

                                              tidbit: my compiler’s class was in 2005, and it was taught in C, using flex/yacc as the lexer/parser. Compilers classes hadn’t all phased out of C by the mid-90s. This was a terrible disservice to the students. We should have used, e.g., Perl.

                                              1. 1

                                                “And that’s just in your own chosen field, which represents such a tiny fraction of all the things there are to know in computer science you might as well never have learned anything at all. Not a single living person knows how everything in your five-year-old MacBook actually works. “

                                                1. 1

                                                  I think we need to separate “getting into the guts of the OS you use” and “writing an OS from scratch”. Your brand new OS won’t be state of the art, but the one you use is.

                                                  Getting to know the guts of the software you frequently use is still a good task to improve your use of it.