22 ms·
John Carmack on inlined code (2014)
- mihaic 2y agoWhen I first heard the maxim that an intelligent person should be able to hold two opposing thoughts at the same time, I was naive to think it meant weighing them for pros and cons. Over time I realized that it means balancing contradictory actions, and the main purpose of experience is knowing when to apply each. Concretely related to the topic, I've often found myself inlining short pieces of one-time code that made functions more explicit, while at other times I'll spend days just breaking up thousand line functions into simpler blocks just to be able to follow what's going on. In both cases I was creating inconsistencies that younger developers nitpick -- I know I did. My goal in most cases now is to optimize code for the limits of the human mind (my own in low-effort mode) and like to be able to treat rules as guidelines. The trouble is how can you scale this to millions of developers, and what are those limits of the human mind when more and more AI-generated code will be used?
- codeflo 2y agoThere’s also the effect that a certain code structure that’s clearer for a senior dev might be less clear for a junior dev and vice versa.
- rob74 2y agoOr rather, senior devs have learned to care more for having clear code rather than (over-)applying principles like DRY, separation of concerns etc., while juniors haven't (yet)...
- stahorn 2y agoWhen you thought you made "smart" solutions and many years later you have to go in and fix bugs in it, is usually when you learn this.
- newswasboring 2y agoThere is a human side to this which I am going through right now. The first full framework I made is proving to be developer unfriendly in the long run, I put more emphasis on performance than readability (performance was the KPI we were trying to improve at the time). Now I am working with people who are new to the codebase, and I observed they were hesitant to criticize it in front of me. I had to actively start saying "lets remove <frame work name>, its outdated and bad". Eventually I found it liberating, it also helped me detach my self worth from my work, something I struggle with day to day.
- orwin 2y agoMy 'principle' for DRY is : twice is fine, trice is worth an abstraction (if you think it has a small to moderate chance to happen again). I used to apply it no matter what, soi guess it's progress...
- whstl 2y agoI really dislike how this principle ends up being used in practice. A good abstraction that makes actual sense is perfectly good even when it's used only once. On the other hand, the idea of deduplicating code by creating an indirection is often not worth it for long-term maintenance, and is precisely the kind of thing that will cause maintenance headaches and anti-patterns. For example: don't mix file system or low-level database access with your business code, just create a proper abstraction. But deduplicating very small fragments of same-abstraction-level can have detrimental effects in the long run.
- ahoka 2y agoI think the main problem with these abstractions that they are merely indirections in most cases, limiting the usefulness to several use cases (sometimes to things that never going to be needed). To quote Dijsktra: "The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise."
- n0w 2y agoI can't remember where I picked it up from, but nowadays I try to be mindful of when things are "accidentally" repeated and when they are "necessarily" repeated. Abstractions that encapsulate the latter tend to be a good idea regardless of how many times you've repeated a piece of code in practice.
- codeflo 2y agoExactly, but distinguishing the two that requires an excellent understanding of the problem space, and can’t at all be figured out in the solution space (i.e., by only looking at the code). But less experienced people only look at the code. In theory, a thousand repetitions would be fine if each one encodes an independent bit of information in the problem space.
- JauntyHatAngle 2y agoI know it's overused, but I do find myself saying YAGNI to my junior devs more and more often, as I find they go off on a quest for the perfect abstraction and spend days yak shaving as a result.
- silisili 2y agoYes! I work with many folks objectively way younger and smarter than me. The two bad habits I try to break them of are abstractions and what ifs. They spend so much time chasing perfection that it negatively affects their output. Multiple times a day I find myself saying 'is that a realistic problem for our use case?' I don't blame them, it's admirable. But I feel like we need to teach YAGNI. Anymore I feel like a saboteur, polluting our codebase with suboptimal solutions. It's weird because my own career was different. I was a code spammer who learned to wrangle it into something more thoughtful. But I'm dealing with overly thoughtful folks I'm trying to get to spam more code out, so to speak.
- reshlo 2y agoI’ve had the opposite experience before. As a young developer, there were a number of times where I advocated for doing something “the right way” instead of “the good enough way”, was overruled by seniors, and then later I had to fix a bug by doing it “the right way” like I’d wanted to in the first place. Doing it the right way from the start would have saved so much time.
- spinningslate 2y agoThis thread is a great illustration of the reality that there are no hard rules, judgement matters, and we don't always get things right. I'm pretty long-in-the-tooth and feel like I've gone through 3 stages in my career: 1. Junior dev where everything was new, and did "the simplest thing that could possibly work" because I wasn't capable of anything else (I was barely capable of the simple thing). 2. Mid-experience, where I'd learned the basics and thought I knew everything. This is probably where I wrote my worst code: over-abstracted, using every cool language/library feature I knew, justified on the basis of "yeah, but it's reusable and will solve lots of stuff in future even though I don't know what it is yet". 3. Older and hopefully a bit wiser. A visceral rejection of speculative reuse as a justification for solving anything beyond the current problem. Much more focus on really understanding the underlying problem that actually needs solved: less interest in the latest and greatest technology to do that with, and a much larger appreciation of "boring technology" (aka stuff that's proven and reliable). The focus on really understanding the problem tends to create more stable abstractions which do get reused. But that's emergent, not speculative ahead-of-time. There are judgements all the way through that: sometimes deciding to invest in more foundational code, but by default sticking to YAGNI. Most of all is seeing my value not as weilding techno armageddon, but solving problems for users and customers. I still have a deep fascination with exploring and understanding new tech developments and techniques. I just have a much higher bar to adopting them for production use.
- sgu999 2y agogood devs*, not all senior devs have learned that, sadly. As a junior dev I've worked under the rule of senior devs who were over-applying arbitrary principles, and that wasn't fun. Some absolute nerds have a hard time understanding where their narrow expertise is meant to fit, and they usually don't get better with age.
- deleted 2y ago[deleted]
- zeroq 2y agoAs someone who recently had to go over a large chunk of code written by myself some 10-15 years ago I strongly agree with this sentiment. Despite being a mature programmer already at that time, I found a lot of magic and gotchas that were supposed to be, and felt at the time, super clever, but now, without a context, or prior version to compare, they are simply overcomplicated.
- devjab 2y agoI find that it’s typically the other way around as things like DRY, SOLID and most things “clean code” are hopeless anti-patterns peddled by people like Uncle Bob who haven’t actually worked in software development since Fortran was the most popular language. Not that a lot of these things are bad as a principle. They come with a lot of “okish” ideas, but if you follow them religiously you’re going to write really bad code. I think the only principle in programming I think can be followed at all times is YAGNI (you aren’t going to need it). I think every programming course, book, whatever should start by telling you to never, ever, abstract things before you absolutely can’t avoid it. This includes DRY. It’s a billion times better to have similar code in multiple locations that are isolated in their purpose, so that down the line, two-hundred developers later you’re not sitting with code where you’ll need to “go to definition” fifteen times before you get to the code you actually need to find. Of course the flip-side is that, sometimes, it’s ok to abstract or reuse code. But if you don’t have to, you should never ever do either. Which is exactly the opposite of what junior developers do, because juniors are taught all these “hopeless” OOP practices and they are taught to mindlessly follow them by the book. Then 10 years later (or like 50 years in the case of Uncle Bob) they realise that functional programming is just easier to maintain and more fun to work with because everything you need to know is happening right next to each other and not in some obscure service class deep in some ridiculous inheritance tree.
- int_19h 2y agoThe problem with repeating code in multiple places is that when you find a bug in said code, it won't actually be fixed in all the places where it needs to be fixed. For larger projects especially, it is usually a worthwhile tradeoff versus having to peel off some extra abstraction layers when reading the code. The problems usually start when people take this as an opportunity to go nuts on generalizing the abstraction right away - that is, instead of refactoring the common piece of code into a simple function, it becomes a generic class hierarchy to cover all conceivable future cases (but, somehow, rarely the actual future use case, should one arise in practice). Most of this is just cargo cult thinking. OOP is a valid tool on the belt, and it is genuinely good at modelling certain things - but one needs to understand why it is useful there to know when to reach for it and when to leave it alone. That is rarely taught well (if at all), though, and even if it is, it can be hard to grok without hands-on experience.
- kolinko 2y agoI bumped into that issue, and it caused a lot of friction between me and 3 young developers I had to manage. Ideas on how to overcome that?
- whstl 2y agoTeaching. I had this problem with an overzealous junior developer and the solution was showing some different perspectives. For example John Ousterhout's A Philosophy of Software Design.
- thelastparadise 2y agoI tried this but they just come back with retorts like "OK boomer" which tends to make the situation even worse. How do you respond to that?
- cbrozefsky 2y agoFire them.
- garblegarble 2y agoThe sibling comment says "fire them". That sounds glib, but it's the correct solution here. From what you've described, you have a coworker who is not open to learning and considering alternative solutions. They are not able to defend their approach, and are instead dismissive (and using an ageist joke to do it). This is toxic to a collaborative work environment. I give some leeway to assholes who can justify their reasoning. Assholes who just want their way because it's their way aren't worth it and won't make your product better.
- yodsanklai 2y agoThis is a discriminatory statement and it should be taken seriously.
- jerf 2y agoTo be honest, at the point where they are being insulting I also agree firing them is a very viable alternative. However, to answer the question more generally, I've had some success first acknowledging that I agree the situation is suboptimal, and giving some of the reasons. These reasons vary; we were strapped for time, we simply didn't know better yet, we had this and that specific problem to deal with, sometimes it's just straight up "yeah I inherited that code and would never have done that", honestly. I then indicate my willingness to spend some time fixing the issues, but make it clear that there isn't going to be a Big Bang rewriting session, but that we're going to do it incrementally, with the system working the whole time, and they need to conceive of it that way. (Unless the situation is in the rare situation where a rewrite is needed.) This tends to limit the blast radius of any specific suggestion. Also, as a senior engineer, I do not 100% prioritize "fixing every single problem in exactly the way I'd do it". I will selectively let certain types of bad code through so that the engineer can have experience of it. I may not let true architecture astronautics through, but as long as it is not entirely unreasonable I will let a bit more architecture than perhaps I would have used through. I think it's a common fallacy of code review to think that the purpose of code review is to get the code to be exactly as "I" would have written it, but that's not really it. Many people, when they see this degree of flexibility, and that you are not riding to the defense of every coding decision made in the past, and are willing to take reasonable risks to upgrade things, will calm down and start working with you. (This is also one of the subtle reasons automated tests are super super important; it is far better for them to start their refactoring and have the automated tests explain the difficulties of the local landscape to them than a developer just blathering.) There will be a set that do not. Ultimately, that's a time to admit the hire was a mistake and rectify it appropriately. I don't believe in the 10x developer, but not for the usual egalitarian reasons... for me the problem is I firmly, firmly believe in the existence of the net-negative developer, and when you have those the entire 10x question disappears. Net negative is not a permanent stamp, the developer has the opportunity to work their way out of it, and arguably, we all start there both as a new developer and whenever we start a new job/position, so let me sooth the egalitarian impulse by saying this is a description of someone at a point in time, not a permanent label to be applied to anyone. Nevertheless, someone who insists on massive changes, who deploys morale-sapping insults to get their way, whose ego is tied up in some specific stack that you're not using and basically insists either that we drop everything and rewrite now "or else", who one way or another refuses to leave "net negative" status... well, it's time to take them up on the "or else". I've exaggerated here to paint the picture clearly in prose, but, then again, of the hundreds of developers I've interacted with to some degree at some point, there's a couple that match every phrase I gave, so it's not like they don't exist at all either.
- skummetmaelk 2y agoThat doesn't seem like holding two opposing thoughts. Why is balancing contradictory actions to optimize an outcome different to weighing pros and cons?
- mihaic 2y agoWhat I meant to say was that when people encounter contradictory statements like "always inline one-time functions" and "breakdown functions into easy to understand blocks", they try to only pick one single rule, even if they consider the pros and cons of each rule. After a while they consider both rules as useful, and will move to a more granular case-by-base analysis. Some people get stuck at rule-based thinking though, and they'll even accuse you of being inconsistent if you try to do case-by-case analysis.
- mjburgess 2y agoTo a certain sort of person, conversation is a game of arriving at these antithesis statements: * Inlining code is the best form of breaking up code. * Love is evil. * Rightwing populism is a return to leftwing politics. * etc. The purpose is to induce aporia (puzzlement), and hence make it possible to evaluate apparent contradictions. However, a lot of people resent feeling uncertain, and so, people who speak this way are often disliked.
- leoh 2y agoYou are probably reaching for Hegel’s concept of dialectical reconciliation
- mihaic 2y agoNot sure, didn't Hegel say that there should be a synthesis step at some point? My view is that there should never be a synthesis when using these principles as tools, as both conflicting principles need to always maintain opposites. So, more like Heraclitus's union of opposites maybe if you really want to label it?
- greenie_beans 2y agothe synthesis would be the outcome maybe? writing code that doesn't follow either rule strictly: > Concretely related to the topic, I've often found myself inlining short pieces of one-time code that made functions more explicit, while at other times I'll spend days just breaking up thousand line functions into simpler blocks just to be able to follow what's going on. In both cases I was creating inconsistencies that younger developers nitpick -- I know I did.
- peepee1982 2y agoThat's exactly what I try to do. I think it's an unpopular opinion though, because there are no strict rules that can be applied, unlike with pure ideologies. You have to go by feel and make continuous adjustments, and there's no way to know if you did the right thing or not, because not only do different human minds have different limits, but different challenges don't tax every human mind to the same proportional extent. I get the impressions that programmers don't like ambiguity in general, let alone in things they have to confront in real life.
- mr_toad 2y ago> there are no strict rules that can be applied The rules are there for a reason. The tricky part is making sure you’re applying them for that reason.
- peepee1982 2y agoI don't know what your comment has to do with my comment.
- tomohawk 2y agoWhat makes an apprentice successful is learning the rules of thumb and following them. What makes a journeyman successful is sticking to the rules of thumb, unless directed by a master. What makes a master successful is knowing why the rules of thumb exist, what their limits are, when to not follow them, and being able to make up new rules.
- moss2 2y ago[flagged]
- navane 2y agoSo this maxim can both be used for good and for bad. Extra points for this maxim.
- wrasee 2y agoA metamaxim?
- echelon 2y agoAs a tool, it's a wedge to break indoctrination and overcome bias. It leads to more pragmatic and less ideological thinking. The subject is compelled to contrast opposing views and consider the merits of each. Any use by ideological groups twists the purpose of the phrase on its head. The quote encourages thinking and consideration. You'd have to turn off your brain for this to have the opposite effect.
- darkwater 2y ago> Any use by ideological groups twists the purpose of the phrase on its head. The quote encourages thinking and consideration. You'd have to turn off your brain for this to have the opposite effect. Well, it would not be too surprising that it can be used to, for example, make people think that they can trust science and also believe in some almighty, unexplainable by science divine entity.
- desdenova 2y agoYou can trust science, but science doesn't cover all of reality. My imaginary friend does, buy my magic book.
- asenchi 2y agoThoughts like this miss the purpose and significance of the maxim being discussed. Science doesn't disprove an "almighty, unexplainable divine entity" any more than an "almighty, unexplainable divine entity" could also provide science as a means to understand the nature of things. Careful you don't fall into the trap of indoctrination. :)
- hibernator149 2y agoWait, isn't that just Doublethink from 1984? Holding two opposing thoughts is a sign that your mental model of the world is wrong and that it needs to be fixed. Where have you heard that maxim?
- HKH2 2y agoIt's not referring to cognitive dissonance.
- perrygeo 2y agoNo you've got it completely backwards. Reality has multiple facets (different statements, all of which can be true) and a mental model that insists on a singular judgement is reductionist, missing the forest for the trees. Light is a wave and a particle. People are capable of good and bad. The modern world is both amazing and unsustainable. etc. Holding multiple truths is a sign that you understand the problem. Insisting on a singular judgement is a sign that you're just parroting catchy phrases as a short cut to thinking; the real world is rarely so cut and dry.
- ragnese 2y ago> My goal in most cases now is to optimize code for the limits of the human mind (my own in low-effort mode) and like to be able to treat rules as guidelines. The trouble is how can you scale this to millions of developers, and what are those limits of the human mind when more and more AI-generated code will be used? I think the truth is that we just CAN'T scale that way with the current programming languages/models/paradigms. I can't PROVE that hypothesis, but it's not hard to find examples of big software projects with lots of protocols, conventions, failsafes, QA teams, etc, etc that are either still hugely difficult to contribute to (Linux kernel, web browsers, etc) or still have plenty of bugs (macOS is produced by the richest company on Earth and a few years ago the CALCULATOR app had a bug that made it give the wrong answers...). I feel like our programming tools are pretty good for programming in the small, but I suspect we're still waiting for a breakthrough for being able to actually make complex software reliably. (And, no, I don't just mean yet another "framework" or another language that's just C with a fancier type system or novel memory management) Just my navel gazing for the morning.
- austin-cheney 2y agoI suppose that depends on the language and the elegance of your programming paradigm. This is where primitive simplicity becomes important, because when your foundation is composed of very few things that are not dependent upon each other you can scale almost indefinitely in every direction. Imagine you are limited to only a few ingredients in programming: statements, expressions, functions, objects, arrays, and operators that are not overloaded. That list does not contain classes, inheritance, declarative helpers, or a bunch of other things. With a list of ingredients so small no internal structure or paradigm is imposed on you, so you are free to create any design decisions that you want. Those creative decisions about the organization of things is how you dictate the scale of it all. Most people, though, cannot operate like that. They claim to want the freedom of infinite scale, but they just need a little help. With more help supplied by the language, framework, whatever the less freedom you have to make your own decisions. Eventually there is so much help that all you do as a programmer is contend with that helpful goodness without any chance to scale things in any direction.
- twh270 2y ago
- grbsh 2y ago> limits of the human mind when more and more AI-generated code will be used We already have a technology which scales infinitely with the human mind: abstraction and composition of those abstractions into other abstractions. Until now, we’ve focused on getting AI to produce correct code. Now that this is beginning to be successful, I think a necessary next step for it to be useful is to ensure it produces well-abstracted and clean code (such that it scales infinitely)
- defaultcompany 2y ago> My goal in most cases now is to optimize code for the limits of the human mind (my own in low-effort mode) I think you would appreciate the philosophy of the Grug Brained Developer: https://grugbrain.dev https://grugbrain.dev
- gspencley 2y agoMy intro to programming was that I wanted to be a game developer in the 90s. Carmack and the others at Id were my literal heroes. Back then, a lot of code optimizations was magic to me. I still just barely understand the famous inverse square root optimization in the Quake III Arena source code. But I wanted to be able to do what those guys were doing. I wanted to learn assembly and to be able to drop down to assembly and to know where and when that would help and why. And I wasn't alone. This is because these optimizations are not obvious. There is a "mystique" to them. Which makes it cool. So virtually ALL young, aspiring game programmers wanted to learn how to do this crazy stuff. What did the old timers tell us? Stop. Don't. Learn how to write clean, readable, maintainable code FIRST and then learn how to profile your application in order to discover the major bottlenecks and then you can optimize appropriately in order of greatest impact descending. If writing the easiest code to maintain and understand also meant writing the most performant code, then the concept of code optimization wouldn't even exist. The two are mutually exclusive, except in specific cases where it's not and then it's not even worth discussing because there is no conflict. Carmack seems to acknowledge this in his email. He realizes that inlining functions needs to be done with careful judgment, and the rationale is both performance and bug mitigation. But that if inlining were adopted as a matter of course, a policy of "always inline first", the results would quickly be an unmaintainable, impossible to comprehend mess that would swing so far in the other direction that bugs become more prominent because you can't touch anything in isolation. And that's the bane of software development: touch one thing and end up breaking a dozen other things that you didn't even think about because of interdependence. So we've come up with design patterns and "best practices" that allow us to isolate our moving parts, but that has its own set of trade-offs which is what Carmack is discussing. Being a 26 year veteran in the industry now (not making games btw), I think this is the type of topic that you need to be very experienced to be able to appreciate, let alone to be able to make the judgment calls to know when inlining is the better option and why.
- tetha 2y agoI had exactly this discussion today in an architectural discussion about an infrastructure extension today. As our newest team member noted, we planned to follow the reference architecture of a system in some places, and chose not to follow the reference architecture in other places. And this led to a really good discussion pulling the reference architecture of this system apart and understanding what it optimizes for (resilience and fault tolerance), what it sacrifices (cost, number of systems to maintain) and what we need. And yes, following the reference architecture in one place and breaking it in another place makes sense. And I think that understanding the different options, as well as the optimization goals setting them apart, allows you to make a more informed decision and allows you to make a stronger argument why this is a good decision. In fact, understanding the optimization criteria someone cares about allows you to avoid losing them in topics they neither understand nor care about. For example, our CEO will not understand the technical details why the reference architecture is resilient, or why other choices are less resilient. And he would be annoyed about his time being wasted if you tried. But he is currently very aware of customer impacts due to outages. And like this, we can offer a very good argument to invest money in one place for resilience, and why we can save money in other places without risking a customer impact. We sometimes follow rules, and in other situations, we might not.
- mandevil 2y agoYes, and it is the engineering experience/skill to know when to follow the "rules" of the reference architecture, and when you're better off breaking them, that's the entire thing that makes someone a senior engineer/manager/architect whatever your company calls it.
- jschrf 2y agoYour newest team member sounds like someone worth holding onto.
- lifeisstillgood 2y agoI often Bang on about “software is a new form of literacy”. And this I feel is a classic example - software is a form of literacy that not only can be executed by a CPU but also at the same time is a way to transmit concepts from one humans head to another (just like writing) And so asking “will AI generated code help” is like asking “will AI generated blog spam help”? No - companies with GitHub copilot are basically asking how do I self-spam my codebase It’s great to get from zero to something in some new JS framework but for your core competancy - it’s like outsourcing your thinking - always comes a cropper (Book still being written)
- debit-freak 2y agoI think a lot of the traditional teachings of "rhetoric" can apply to coding very naturally—there's often practically unlimited ways to communicate the same semantics precisely, but how you lay the code out and frame it can make the human struggle to read it straightforward to overcome (or near-impossible, if you look at obfuscation).
- davidw 2y ago> is a way to transmit concepts from one humans head to another (just like writing) That's almost its primary purpose in my opinion... the CPU does not care about Ruby vs Python vs Rust, it's just executing some binary code instructions. The code is so that other people can change and extend what the system is doing over time and share that with others.
- rileymat2 2y agoI get your point, but often the binary code instructions between those is vastly different.
- davidw 2y agoThe fact that we work with the high level languages rather than the binary code, despite all their inefficiencies, speaks to the human aspect being pretty important in the equation.
- j7ake 2y agoTo make an advance in a field, you must simultaneously believe in what’s currently known as well as distrust that the paradigm is all true. This gives you the right mindset to focus on advancing the field in a significant way. Believing in the paradigm too much will lead to only incremental results, and not believing enough will not provide enough footholds for you to work on a problem productively.
- hnuser123456 2y agoOn a positive note, most AI-gen code will follow a style that is very "average" of everything it's seen. It will have its own preferred way of laying out the code that happens to look like how most people using that language (and sharing the code online publicly), use it.
- SoftTalker 2y ago> other times I'll spend days just breaking up thousand line functions into simpler blocks just to be able to follow what's going on Absolutely, I'll break up a long block of code into several functions, even if there is nowhere else they will be called, just to make things easier to understand (and potentially easier to test). If a function or procedure does not fit on one screen, I will almost always break it up. Obviously "one screen" is an approximation, not all screens/windows are the same size, but in practice for me this is about 20-30 lines.
- hinkley 2y agoThat’s undoubtedly a Zelda Fitzgerald quote (her husband plagiarized her shamelessly). As a consequence of the Rule of Three, you are allowed to have rules that have one exception without having to rethink the law. All X are Y except for Z. I sometimes call this the Rule of Two. Because it deserves more eyeballs than just being a subtext of another rule.
- xnx 2y ago> I was creating inconsistencies that younger developers nitpick Obligatory: “A foolish consistency is the hobgoblin of little minds" Continued because I'd never read the full passage: "... adored by little statesmen and philosophers and divines. With consistency a great soul has simply nothing to do. He may as well concern himself with his shadow on the wall. Speak what you think now in hard words, and to-morrow speak what to-morrow thinks in hard words again, though it contradict every thing you said to-day. — 'Ah, so you shall be sure to be misunderstood.' — Is it so bad, then, to be misunderstood? Pythagoras was misunderstood, and Socrates, and Jesus, and Luther, and Copernicus, and Galileo, and Newton, and every pure and wise spirit that ever took flesh. To be great is to be misunderstood.” ― Ralph Waldo Emerson, Self-Reliance: An Excerpt from Collected Essays, First Series
- JamesBarney 2y agoMy go to heuristic for how to break up code is white board or draw up in lucidchart your solution to explain it to another dev. If your methods don't match the whiteboard refactor.
- gorgoiler 2y ago> Inlining functions also has the benefit of not making it possible to call the function from other places. I’ve really gone to town with this in Python. def parse_news_email(…): def parse_link(…): … def parse_subjet(…): … … If you are careful, you can rely on the outer function’s variables being available inside the inner functions as well. Something like a logger or a db connection can be passed in once and then used without having to pass it as an argument all the time: # sad def f1(x, db, logger): … def f2(x, db, logger): … def f3(x, db, logger): … def g(xs, db, logger): for x0 in xs: x1 = f1(x0, db, logger) x2 = f2(x1, db, logger) x3 = f3(x2, db, logger) yikes x3 # happy def g(xs, db, logger): def f1(x): … def f2(x): … def f3(x): … for x in xs: yield f3(f2(f1(x))) Carmack commented his inline functions as if they were actual functions. Making actual functions enforces this :) Classes and “constants” can also quite happily live inside a function but those are a bit more jarring to see, and classes usually need to be visible so they can be referred to by the type annotations.
- eru 2y agoFunny enough, the equivalent of your Python example is how Haskell 'fakes' all functions with more than one argument (at least by default). Imperative blocks of code in Haskell (do-notation) also work like this.
- InDubioProRubio 2y agoSo inlining is the private of functions without a object. Pop it all to stack, add arguments, set functionpointer to instructionstart of inline code, challenge accepted, lets go to..
- bitwize 2y agoIt's possible to nest subprograms within subprograms in Ada. I take advantage of this ability to break a large operation into one or more smaller simpler "core" operations, and then in the main body of the procedure write some setup code followed by calls to the core operation(s).
- raverbashing 2y agoIt might be a benefit in some cases, but I do feel that f1/f2/f3 are the prime candidates for actual unit testing
- rossant 2y ago(2014)
- mindsuck 2y agoFirst discussed here back then: https://news.ycombinator.com/item?id=8374345 https://news.ycombinator.com/item?id=8374345
- dang 2y agoThanks! Macroexpanded: John Carmack on Inlined Code - https://news.ycombinator.com/item?id=39008678 https://news.ycombinator.com/item?id=39008678 - Jan 2024 (2 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=33679163 https://news.ycombinator.com/item?id=33679163 - Nov 2022 (1 comment) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=25263488 https://news.ycombinator.com/item?id=25263488 - Dec 2020 (169 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=18959636 https://news.ycombinator.com/item?id=18959636 - Jan 2019 (105 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=14333115 https://news.ycombinator.com/item?id=14333115 - May 2017 (2 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=12120752 https://news.ycombinator.com/item?id=12120752 - July 2016 (199 comments) John Carmack on Inlined Code - https://news.ycombinator.com/item?id=8374345 https://news.ycombinator.com/item?id=8374345 - Sept 2014 (260 comments)
- mcosta 2y ago2007
- gdiamos 2y agoHow much of this is specific to control loops that execute at 60hz?
- lloeki 2y agoNone. > The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!). Some years ago at job foo I wrote a Ruby library that was doing some stuff. Time was of the essence, I was a one-man team, and the trickiness of it required a clear understanding of the details, so I wrote a single ~1000 LOC file comprising of the entirety of the namespace module of that library, with but a couple or three functions. Then a new hire joined my one-man team. I said: apologies for this unholy mess, it's overdue for a refactoring, with a bunch of proper classes with small methods and split in a few files accordingly. They said: not at all, the code was exceptionally clear; I could sit and understand every bit of it down to the grittier critical details in under an hour, and having seen it written this way it is obvious to me that these details of interactions would not have been abstracted away, but obscured away.
- yoz 2y agoYour final ten words of the comment are a perfectly concise explanation of the problem; thank you! And it drives home something I often forget about why code units should do Only One Thing.
- Cthulhu_ 2y agoThing is, a lot of developers see long code and think "this is a Bad Thing" because of dogma, but in practice, a lot of developers never actually wrote anything nontrivial like that.
- otikik 2y agoI have worked with many developers and I have seen them follow two distinct paths when encountering complex code. There's one camp that wants to use abstractions and names, and there's another (in my experience, smaller) camp which prefers to have as few abstractions as possible, and "every gritty detail visible". I think both strategies have advantages and disadvantages. The group that likes abstractions can "ignore parts of the code" quickly, which potentially makes them "search" faster. If there's a bug that needs fixing, or a new feature that needs to be added, they will reach the part of the code that will need modifications faster. The detail-oriented people can take a bit longer to identify the code that needs modification, but they also tend to be able to make those modifications faster. They also tend to be be great "spelunkers". They seem to have a "bigger cache", so to speak. But it is not infinite. They will eventually not be able to hold all the complexity in their heads, just like the first group. It will just take a bit longer. I am firmly on the first group and that is how I write my code. I have been fortunate enough to encounter enough people from the other group to know not to diss their code immediately, and to appreciate it for its merits. When working in a team with both kinds of personalities one has to make compromises ("please remove all of these 1-line functions, Jonathan will hate them", and "could you split this 3k lines function into 2 or 3 smaller ones, for easier review?").
- dehrmann 2y agoAlways read older stuff from Carmack remembering the context. He made a name for himself getting 3D games to run on slow hardware. The standard advice of write for clarity first, make sure algorithms have reasonable runtimes, and look at profiler data if it's slow is all you need 99% of the time.
- Cthulhu_ 2y agoAnd before that, 2D games (side-scrolling platformers were not a thing on PC hardware until Carmack did it, iirc). I think his main thing is balancing clarity - what happens when and in what order - with maintainability. Compare this with enterprise software, which is orders of magnitude more complex than video games in terms of business logic (the complexity in video games is in performance optimization), but whose developers tend to add many layers of abstraction and indirection, so the core business process is obfuscated, or there's a billion non-functional side activities also being applied (logging, analytics, etc), again obfuscating the core functionality. It's fun to go back to more elementary programming things, in e.g. Advent of Code challenges or indeed, game development.
- nadam 2y ago"compare this with enterprise software, which is orders of magnitude more complex than video games in terms of business logic" Maybe this was true 20 years ago, but I do not think this is true today. Game code of some games is almost as complex as enterprise software or even more complex in some cases (think of grand strategy games like Civilization or Paradox games). The difference is that it still needs to be performant, so the evolutionary force just kills programmers and companies creating unperformant abstractions. In my opinion game programming is just harder than enterprise programming if we speak about complex games. (I have done both). The only thing which is easier in game programming is that it is a bit easier to see clearly in terms of 'business requirements', and also it is more meritocratic (you can start a game company anywhere on the globe, no need to be at business centers.) And of course game programming is more fun, so programmers do the harder job even for less money. For people who think game programming is less complex than enterprise software, I suggest the CharacterMovementComponent class in unreal engine which is the logic of movement of characters (people) in a networked game environment... With multiple thousand lines of code in just the header is not uncommon in unreal. And this is not complex because of optimization mostly. This is very complex and messy logic. Of course we can argue that networking and physics could be done in a simple naive way, which would be unacceptable in terms of latency and throughput, so all in all complexity is because of optimization after all. But it is not the 'fun' elegant kind of optimization, it is close to messy enterprise software in some sense in my opinion.
- Cthulhu_ 2y agoI feel like this style is also encouraged in Go and / or the clean/onion architecture / DDD, to a point, where the core business logic can and should be a string of "do this, then do that, then do that" code. In my own experience I've only had a few opportunities to do so (most of my work is front-end which is a different thing entirely), the one was application initialisation (Create the logger, then connect to the database, then if needed initialize / migrate it, then if needed load test data. Then create the core domain services that uses the database connection. Then create the HTTP handlers that interface with the domain services. Then start the HTTP server. Then listen for an end process command and shut down gracefully), the other was pure business logic (read the database, transform, write to file, but "database" and "file" were abstract concepts that could be swapped out easily). You don't really get that in front-end programming though, it's all event driven etc.
- xxs 2y ago> the one was application initialisation ...and then you want to parallelize as much as possible to allow for fast boot times which helps the development process immensely. One of the things I've learned is that optimizing for developer quality of life is one of the best approaches when it comes to correctness and performance. Then, the developers would be able to run multiple iterations of the real thing.
- ninetyninenine 2y agoHis overall solution highlighted in the intro is that he's moved on from inlining and now does pure functional programming. Inlining is only relevant for him during IO or state changes which he does as minimally as possible and segregates this from his core logic. Pure functional programming is the bigger insight here that most programmers will just never understand why there's a benefit there. In fact most programmers don't even completely understand what FP is. To most people FP is just a bunch of functional patterns like map, reduce, filter, etc. They never grasp the true nature of "purity" in functional programming. You see this lack of insight in this thread. Most responders literally ignore the fact that Carmack called his email completely outdated and that he mostly does pure FP now.
- deleted 2y ago[deleted]
- wruza 2y agoSome grasp it but see its trade-off contract, which is demanding.
- ninetyninenine 2y agoWith practice it just becomes another paradigm of programming. The trade off is really a skill issue from this perspective. The larger issue is performance which is a legitimate reason for not using fp in many cases. But additionally in many cases there is no performance trade off.
- nuancebydefault 2y agoI've never seen pure FP...
- nuancebydefault 2y agoI might add Carmack's take on functional programming https://web.archive.org/web/20120501221535/http://gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php https://web.archive.org/web/20120501221535/http://gamasutra....
- randomtoast 2y agoMy browser says "The connection to number-none.com is not secure". Guess it is only a matter of time until HTTPS becomes mandatory.
- otikik 2y ago> I have gotten much more bullish about pure functional programming, even in C/C++ where reasonable: (link) The link is no longer valid, I believe this is the article in question: https://www.gamedeveloper.com/programming/in-depth-functional-programming-in-c- https://www.gamedeveloper.com/programming/in-depth-functiona...
- ninetyninenine 2y agoProbably the more important link. He's basically saying his old email is outdated and he does pure FP now.
- meheleventyone 2y agoThis is over a decade old at this stage, it would be interesting to know how his thoughts have evolved since.
- wruza 2y agoI wish languages had the following: let x = block { … return 5 } // x == 5 And the way to mark copypaste, e.g. common foo { asdf(qwerty(i+j)); printf(“%p”, write)); bar(); } …(repeats verbatim 20 times)… … common foo { asdf(qwerty(i+k)); printf(“%d”, (int)write); // cast to int bar(); } … And then you could `mycc diff-common foo` and see: <file>:<line>: common <file>:<line>: common … <file>:<line>: @@…@@ -asdf(qwerty(i+j)); +asdf(qwerty(i+k)); @@…@@ -printf(“%p”, write)); +printf(“%d”, (int)write); // cast to int With this you can track named common blocks (allows using surrounding context like i,j,k). Without them being functions and subject for functional entanglement $subj discusses. Most common code gets found out and divergences get bold. IDE support for immediate highlighting, snippeting and auto-common-ing similar code would be very nice. Multi-patching common parts with easily reviewing the results would also be great. Because the bugs from calling a common function arise from the fact that you modify it and it suddenly works differently for some context. Well, you can comment a common block as fragile and then ignore it while patching: common foo { // @const: modified and fragile! … } You still see differences but it doesn’t add in a multi-patch dialog. Not expecting it to appear anywhere though, features like that are never considered. Maybe someone interested can feature it in circles? (without my name associated)
- badmintonbaseba 2y agoIn C++ it's an idiom to use immediately invoked lambdas: auto x = []{ /*...*/ return 5; }(); There is/was an attempt to introduce a more of a first-class language construct for such immediate "block expressions": https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2806r2.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p28... I'm not convinced that automatic checking of copy-paste errors of such blocks make much sense though. At least I think the false positive rate would be way too high.
- wruza 2y agoIIFE exist, but are cumbersome to type/read in most languages. C++ is probably the winner by syntax and semantics here. false positive rate would be way too high The key idea is not to have identical blocks, but to have a way to overview changes in similar code, similar by origin and design. It’s a snippet diff tool, not a typo autocorrector. There’s no false positives cause if “common foo” has zero diff in all cases, it probably should be “foo(…)”.
- Ono-Sendai 2y agoThere is actually a major problem with long functions - they take a long time to compile, due to superlinear complexity in computation time as a function of function length. In other words breaking up a large function into smaller function can greatly reduce compile times.
- badmintonbaseba 2y agoThat honestly feels like a minor problem, and not something to optimize for. Also an aggressively inlining compiler will experience exactly the same problem. AFAIK at least clang always inlines a static (as in internal linkage) function if it's used only once in the translation unit, no matter how large it is.
- Ono-Sendai 2y agoVisual studio doesn't do that inlining. And it is a significant problem, I have had to refactor my code into multiple functions because of it.
- badmintonbaseba 2y agoIt might be a significant problem, but not in the code, but the compiler. Fair enough, you are working around a compiler issue.
- Ono-Sendai 2y agoIf you consider any superlinear complexity a 'compiler issue' I guess.
- badmintonbaseba 2y agoIt absolutely is, if it makes compile times unreasonable for reasonable code. Compilers have to make trade-offs like this all the time, they can't use overly excessive optimizations.
- nuancebydefault 2y ago> The function that is least likely to cause a problem is one that doesn’t exist, which is the benefit of inlining it. I think that summarizes the case pro inlining.
- physicsguy 2y agoI think when developing something from scratch, it's actually not a terrible strategy to do this and pick out boundaries when they become clearer. Creating interfaces that make sense is an art, not a science.
- IshKebab 2y agoI think the major problem with this is scope. Now a variable declared at the top of your function is in scope for the entire function. Limiting scope is one of the best tools we have to prevent bugs. It's one reason why we don't just use globals for everything.
- wheybags 2y agoYou can artificially create scope. I often write code like: Foo f = null; { ... stuff with variables f = barbaz; }
- SJC_Hacker 2y agoIs the point that any var declared in between the braces automatically goes out of scope, to minimize potential duplication of var names and unintended behavior ? The worst I've seen is old school C programmers who insisted on reusing loop variables in other loops. Even worse, those loop variables were declared inside the loop declaration, which old C standards allowed to visible outside of it. So they would have stuff like this for(int i=0; i<10; i++) { ... } for (;i<20;i++) { ... } Later versions of C++ disallowed this, which led to some interesting compile failures, which led to insistence of the old stubborn programmers that new compilers simply not be used
- IshKebab 2y agoNow you have to make `f` nullable and you run the risk of not initialising it and getting a null pointer. You can't do it in C, but in functional style languages you can do this: let f = { let bar = ...; let baz = ...; let barbaz = ...; barbaz }; Which is a lot nicer. But if you ask me it's just a function by another name except it still doesn't limit scope quite as precisely as a function.
- nikki93 2y agoGCC and clang (and maybe others) have 'statement expressions': https://godbolt.org/z/sqYnbh4Ej https://godbolt.org/z/sqYnbh4Ej
- jjallen 2y agoOne benefit that I can think of for inlined code is the ability to "step" through each time step/tick/whatever and debug the state at each step of the way. And one drawback I can think of is that when there are more than something like ten variables finding a particular variable's value in an IDE debugger gets pretty difficult. It would be at this point that I would use "watches", at least in the case of Jetbrains's IDEs. But then yeah you can also just log each step in a custom way verifying the key values are correct which is what I am doing as we speak.
- deleted 2y ago[deleted]
- mellosouls 2y ago(2014) Ten years ago - a long time in coding.
- rm445 2y agoIt's at least twenty new web frameworks, but maybe not so long in low-level stuff. You can probably rely on C99 being available now more than you could in 2014.
- sylware 2y agoI have been super picky about what JC says since he moved the ID engine from plain and simple C99 to c++.
- rickreynoldssf 2y agoThe clean code people are losing their collective minds reading that. lol
- randomdata 2y agoWhy's that? Uncle Bob seems pretty clear that most of your code should be free of side effects, and that necessary state mutation should be isolated to one place. Carmack is saying the same thing.
- rickreynoldssf 2y agoHe's definitely not talking about 5 line functions and DRY.
- shortrounddev2 2y agoCan someone explain what inlined means here? It was my assumption that the compiler will automatically inline functions and you don't need to do it explicitly. Unless it means something else in this context
- AlotOfReading 2y agoIt means not using explicit functions, just writing the same code as little inline blocks inside the main function because it allows you to see all the things that would be hidden if all the code wasn't immediately visible. To the other point though, the quality of compiler inlining heuristics is a bit of a white lie. The compiler doesn't make optimal choices, but very few people care enough to notice the difference. V8 used a strategy of considering the entire source code function length (including comments) in inlining decisions for many years, despite the obvious drawbacks.
- shortrounddev2 2y agoWell there's also compiler directives other than `inline`, like msvc's `__inline` and `__forceinline` (which probably also have an equivalent in gcc or clang), so personally I don't think you need to make the tradeoff between readability and reusability while avoiding function calls. Not to mention C++ constevals and C-style macros, though consteval didn't exist in 2007
- AlotOfReading 2y ago__forceinline is purely a suggestion to the compiler, not a requirement. Carmack's point isn't about optimizing the costs of function calls though. It's about the benefits to code quality by having everything locally visible to the developer.
- shortrounddev2 2y agoIt's an interesting view because I find neatly compartmentalized functions easier to read and less error prone, though he does point out that copying chunks of code such as vector operations can lead to bugs when you forget to change some variable. I guess it depends on the function. Something like Vector c = dotProduct(a, b); is readable enough and doesn't warrant inlining, I think. There's nothing about `dotProduct` that I would expect to have any side effects, especially if its prototype looks like: Vector dotProduct(Vector const& a, Vector const& b) const;
- exodust 2y agoFor some reason this quote by Carmack stands out for me: > "it is often better to go ahead and do an operation, then choose to inhibit or ignore some or all of the results, than try to conditionally perform the operation." I'm not the audience for this topic, I do javascript from a designer-dev perspective. But I get in the weeds sometimes, maxing out my abilities and bogged down by conditional logic. I like his quote it feels liberating... "just send it all for processing and cherry-pick the results". Lightbulb moment.
- wiz21c 2y agomy CPU does that all of the time, it is speculative execution :-)
- deleted 2y ago[deleted]
- VyseofArcadia 2y ago> That was a cold-sweat moment for me: after all of my harping about latency and responsiveness, I almost shipped a title with a completely unnecessary frame of latency. In this era of 3-5 frame latency being the norm (at least on e.g. the Nintendo Switch), I really appreciate a game developer having anxiety over a single frame.
- pragma_x 2y agoTo be fair, back in 2014 that was one frame at 60Hz or slower for some titles. At 80-120Hz, 3-5 frames is comparatively similar time.
- 01HNNWZ0MV43FF 2y agoI don't think high frame rates are common outside of PC gaming yet. Wikipedia indicates the Switch maxes out at 1080p60, and the newest Zelda only at 900p30 even when docked https://en.m.wikipedia.org/wiki/Nintendo_Switch https://en.m.wikipedia.org/wiki/Nintendo_Switch
- VyseofArcadia 2y agoI believe both the PS5 and whatever nonsense string of Xs, numbers, and descriptors MS named this gen's console can do 144Hz output. I don't know how many games take advantage of that or whether that refresh rate is common on TVs.
- philistine 2y ago60 FPS isn't even promised on PS5 Pro. Most graphically demanding titles still aim for 30 FPS on consoles, with any game able to support 60 FPS consistently worth noting.
- Narishma 2y agoWhat they said is true. There are some games with 120 FPS modes on PS5 and Series X, maybe even series S. That doesn't mean every game (or even most) are like that, just that the hardware supports it. At the end of the day you can't stop developers targeting whatever framerate they want.
- donatj 2y agoI have a coworker that LOVES to make these one or two line single use functions that absolutely drives me nuts. Just from a sheer readability perspective being able to read a routine from top to bottom and understand what everything is doing is invaluable. I have thought about it many times, I wish there was an IDE where you could expand function calls inline.
- wodenokoto 2y agoIt’s called “self documenting code” and the way you self document code it is by taking all your comments and make them into functions, named after your would-be comment. I’m not a fan either.
- MetaWhirledPeas 2y agoEverything must be done to taste. I think code can be made "self-documenting" without going overboard and doing silly things.
- Tyr42 2y agoSometimes it's easier to define some vocabulary and then use it. Like defining push and pop on a stack vs stack[++ix] = blah and blah = stack[ix--]. And avoids needing to think about it being prefix or postfix after you don't that one time. But at other times it's insufferable, when the abstraction is leaky and unintuitive.
- zelphirkalt 2y agoThis can be done in a good way and in bad ways. With most code you will be calling builtin procedures/functions. You also don't look under the hood for those usually. But for the code of your coworker it seems to irritate you. This could mean many things. Just to name a few: (1) The names are not giving a good idea what those functions do. (2) The level of abstraction is not the same inside the calling function, so that you feel the need to check the implementation detail of those small functions. (3) You don't trust the implementation of those smaller functions. (4) The separated out functions could be not worth separating out and being given names, because what the code in them does is clear enough without them being separated out. (n) or some other reason. The issue does not have to be that those things are split out into separate small functions. The issue might be something else.
- BenoitEssiambre 2y agoHere are some information theoretic arguments why inlining code is often beneficial: https://benoitessiambre.com/entropy.html https://benoitessiambre.com/entropy.html In short, it reduces scope of logic. The more logic you have broken out to wider scopes, the more things will try to reuse it before it is designed and hardened for broader use cases. When this logic later needs to be updated or refactored, more things will be tied to it and the effects will be more unpredictable and chaotic. Prematurely breaking out code is not unlike using a lot of global variables instead of variables with tighter scopes. It's more difficult to track the effects of change. There's more to it. Read the link above for the spicy details.
- norir 2y agoThis is why I think it's a mistake that many popular languages, including standard c/c++, do not support nested function definitions. This for me is the happy medium where code can be broken into clear chunks, but cannot be called outside of the intended scope. A good compiler can also detect if the nested function is only called once and inline it.
- badmintonbaseba 2y agoC++ has lambdas and local classes. Local classes have some annoying arbitrary limitations, but they are otherwise useful.
- knodi123 2y agoAfter spending a lot of time writing idiomatic React components in es6, I've found my love of locally declared lambdas to really grow. If I give the lambdas really good names, I find that the main body of my component is very, very readable, even more so than if I'd used a more traditional style liberally sprinkled with comments.
- zelphirkalt 2y agoGiving your lambdas names defeats part of their purpose though.
- deleted 2y ago[deleted]
- endlessmike89 2y agoLink to the Wayback Machine cache/mirror, in case you're also experiencing a "Bad Gateway/Connection refused" error https://web.archive.org/web/20241009062005/http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html https://web.archive.org/web/20241009062005/http://number-non...
- oglop 2y agoOh good, a FP post. I love watching people argue over nothing. Here’s the actual rule, do what works and ships. Don’t posture. Don’t lament. Don’t idealize. Just solve the fucking problem with the tool and method that fits and move on. And do not try to use this comment threat to understand FP. Too many cooks, and most of the are condescending douchebags. Go look at Wikipedia or talk with an AI about it. Don’t ask this place, it’s all just lectures and nitpicks.
- eapriv 2y ago“Comment threat” is a nice one.
- adamrezich 2y agoThis isn't actually an FP post.
- freilanzer 2y agoIronically, this comment adds nothing to the post and is needlessly belligerent and condescending.
- easeout 2y agoCome to think of it, execute-and-inhibit style as described here is exactly what's going on when in continuous deployment you run your same pipeline many times a day with small changes, and gate new development behind feature flags. We're familiar with the confidence derived from frequently repeating the whole job.
- adamrezich 2y agoI find that when initially exploring a problem space, it's useful to consider functions as “verbs” to help me think through the solution, and that feels useful in helping me figure out a solution to my problem—I've isolated some_operation() into its own function, and it's easy to see at a glance whether or not some_operation() does the specific thing its name claims to do (and if so, how well). But then after things have solidified somewhat, it's good practice to go back through your code and determine whether those “verbs” ended up being used more than once. Quite often, something that I thought would be repeated enough to justify being its own function, is actually only invoked in one specific place—so I go back and inline these functions as needed. The less my code looks like a byzantine tangle of function invocations, and the more my code reads like a straightforward list of statements to execute in order, the better it makes me feel, because I know that I'm not unnecessarily hiding complexity, and I can get a better, more concrete feel for what my program's execution looks like.
- dang 2y agoRelated: John Carmack on Inlined Code - https://news.ycombinator.com/item?id=39008678 https://news.ycombinator.com/item?id=39008678 - Jan 2024 (2 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=33679163 https://news.ycombinator.com/item?id=33679163 - Nov 2022 (1 comment) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=25263488 https://news.ycombinator.com/item?id=25263488 - Dec 2020 (169 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=18959636 https://news.ycombinator.com/item?id=18959636 - Jan 2019 (105 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=14333115 https://news.ycombinator.com/item?id=14333115 - May 2017 (2 comments) John Carmack on Inlined Code (2014) - https://news.ycombinator.com/item?id=12120752 https://news.ycombinator.com/item?id=12120752 - July 2016 (199 comments) John Carmack on Inlined Code - https://news.ycombinator.com/item?id=8374345 https://news.ycombinator.com/item?id=8374345 - Sept 2014 (260 comments)
- kragen 2y agoThere is a longer version of this thought-provoking post, also including Carmack's thoughts in 02012, at https://cbarrete.com/carmack.html https://cbarrete.com/carmack.html. But maybe that version has not also had threads about it.
- dang 2y agoIt doesn't seem to have, since https://news.ycombinator.com/from?site=cbarrete.com https://news.ycombinator.com/from?site=cbarrete.com is empty. Should we change the top link to that URL?
- kragen 2y agoI do think it's a better page, but I wouldn't change the link if I were in charge. On the other hand, I think everyone is grateful that you're in charge of HN and not me. Especially not me. So I think you should use your judgment.
- fabiensanglard 2y agoHow does a program work when its disallow "backward branches". Same thing with "subroutine calls" how do you structure a program without them?
- kragen 2y agoWell, you have one backward branch at the end of the program, and you inline your subroutines. I'm pretty sure you've written shaders for ancient GPUs that had similar limitations? And anything you can do in hardware you can do without subroutine calls, and in hardware the loop starts again on every clock cycle.
- bdowling 2y agoYou can do a lot with a program that looks like: while(1) { if (condition1) ... if (condition2) ... // etc } Subroutine calls can be eliminated by inlining everything, using macros to make the code more manageable. Loops can be simulated using macros that expand to multiple copies of the code, one for each step. One advantage is that the program will never get into an unbounded loop because the program counter will always advance towards the end of the main loop.
- Jtsummers 2y agoIt allows one backward branch. Think of it like hand-rolling your OS scheduler for processes/threads. You also have to track your "program counter" yourself. As a silly example: typedef enum state {EVEN, ODD} state_t; state_t task1 = EVEN; state_t task2 = EVEN; while (1) { switch(task1) { case EVEN: // do even things task1 = ODD; break; case ODD: // do odd things task1 = EVEN; break; default: fprintf(stderr, "WTF?\n"); exit(1); } switch(task2) { case EVEN: // do even things task2 = ODD; break; case ODD: // do odd things task2 = EVEN; break; default: fprintf(stderr, "WTF?\n"); exit(1); } } For every "process" you've unrolled like this, you have to place it into its own switch/case or call out to a function which has similar logic (when subroutines aren't disallowed). If the process is short enough you let it execute all the way through, bigger processes would need to be broken apart like above to avoid consuming an entire cycle's time (especially important in real-time systems).
- deleted 2y ago[deleted]
- low_tech_love 2y agoInteresting: this is a 2014 post from Jonathan Blow reproducing a 2014 comment by John Carmack reproducing a 2007 e-mail by the same Carmack reproducing a 2006 conversation (I assume also via e-mail) he had with a Henry Spencer reproducing something else the same Spencer read a while ago and was trying to remember (possibly inaccurately?). I wonder what is the actual original source (from Saab, maybe?), and if this indeed holds true?
- EGreg 2y agoIs this kind of like 300 was a movie about a Frank Miller novel about a Greek legend about the actual Battle of Thermopylae?
- lencastre 2y agoI’m not even pretending I understood Carmack’s email/mailing list post but if more intelligent/experienced programmers than me care to help me out, what exactly is meant by this he wrote in 2007: _If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that._ Thanks,
- tcoville 2y agoThis is a heavily simplified version of what I'm suspecting he's trying to portray, key this wouldn't be useful for utility functions like string manipulation but more business logic being used across similar functions: def processOrder(): # Some common processing logic print("Processing the order...") def placeOnlineOrder(): processOrder() print("Sending confirmation email...") def placeInStoreOrder(): processOrder() print("Printing receipt...") # Calls from different locations placeOnlineOrder() placeInStoreOrder() Could become: def processOrder(order_type): # Common processing logic print("Processing the order...") if order_type == "online": print("Sending confirmation email...") elif order_type == "in_store": print("Printing receipt...") # Unified calls with different flags processOrder("online") processOrder("in_store")
- fluoridation 2y agoThat... looks decidedly worse. Now you have fewer functions that need to be concerned with multiple unrelated things for no reason.
- kazinator 2y agoIn my opinion, there is value in functions that have only one caller: it's called functional decomposition. The right granularity of functional decomposition can make the logic easier to understand. To prevent unintended uses of a helper function in C, you can make it static. Then at least nothing from outside of that translation unit can call it.
- atulvi 2y agowho read this in John Carmack's voice?
- ydnaclementine 2y ago> do always, then inhibit or ignore strategy can anyone expound on this? I'm not sure what he's exactly referring to here
- rcv 2y ago> The fly-by-wire flight software for the Saab Gripen (a lightweight fighter) went a step further... I would love to hear some war stories about the development of flight software. A lot of it is surely classified, but I'm fascinated by how those systems are put together.
- deleted 2y ago[deleted]
- torginus 2y ago"Typically I am there to rail against the people that talk about using threads and an RTOS for such things, when a simple polled loop that looks like a primitive video game is much more clear and effective. " Yess, I finally feel vindicated. I've been having this argument with embedded people since forever. I was of the opinion that if million line big boy PC apps can make do with just one thread, having fifteen threads and synchronizing between them using mutexes and condition variables on a microcontroller with 64kb RAM is just bonkers. For some reason, the statement that a while(true) loop + ISRs + DMA can do everything an RTOS like FreeRTOS can do, can rile up embedded folks to no end.
- Myrmornis 2y agoThis is the real religious war among programmers -- it's a genuinely consequential question: someone who favors abstraction and modularity is going to absolutely hate working in a codebase with pervasively inlined code. It's clear that Carmack's article is addressing a particular sort of C++ codebase that might be familiar to game developers, but isn't familiar to a lot of us here who work on web applications and backend distributed systems. His "functions" aren't really what we think of as functions: they're clearly mutating huge amounts of global state. They sound more like highly undisciplined methods on large namespaces. You can see that from the following quotes: > There might be a FullUpdate() function that calls PartialUpdateA(), and PartialUpdateB(), but in some particular case you may realize (or think) that you only need to do PartialUpdateB(), and you are being efficient by avoiding the other work. Lots and lots of bugs stem from this. Most bugs are a result of the execution state not being exactly what you think it is. > if a function only references a piece or two of global state, it is probably wise to consider passing it in as a variable. In the world of many people here, i.e. away from Carmack's C++ game dev codebases of the 2000s with huge amounts of global mutable state, the standard common sense still applies: we invented structured programming with functions for profoundly important reasons: modularity and abstraction. Those reasons haven't gone away; use functions. - In a large codebase you do not need or want to read the full tree of implementation in one go. Use functions: they have return types; you know what they do. A substantial piece of implementation should be written as a sequence of calls to subfunctions with very carefully chosen names that serve as documentation in themselves. - Make your functions as pure as possible subject to performance considerations etc. - This brings a huge advantage to helper functions over inlining: it is now easy to see which variables in the top-level function are being mutated. - The implementation is much harder to understand in a single function with 10 mutable variables, than in two functions with 5 mutable variables. I think ultimately that's just a fact of combinatorics; not something we can hold opinions about. - But sure, if the 10 mutable variables cannot be decomposed into two independent modules then don't create spurious functions. - A separate function is testable; a block inside a function is not. It wasn't really clear that the sort of test suites that many of us here work with were part of Carmack's codebases at all! - It is absolutely fine to use a function if it improves modularity / readability even if it only called once.
- roeles 2y ago> No bug has ever been found in the “released for flight” versions of that code. I thought that at least his crash was a result of bad constants in flight software: https://www.youtube.com/watch?v=SWZLmVqNaQc https://www.youtube.com/watch?v=SWZLmVqNaQc The first comment appears to agree with me.
- djha-skin 2y agoThis largely concurs with clean architecture[1], especially considering his foreword containing hindsight. Clean architecture can be summarized thusly: 1. Bubble up mutation and I/O code. 2. Push business logic down. This is how it's stated in [1]: > The concentric circles represent different areas of software. In general, the further in you go, the higher level the software becomes. The outer circles are mechanisms. The inner circles are policies. Inlining as a practice is in service of #1, while factoring logic into pure functions addresses #2, noted in the foreword: > The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!). 1: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-a...