Live data from Hacker News

John Carmack's comment on Doom 3's code style

kotaku.com

171–180 of 210 posts

Re: John Carmack's comment on Doom 3's code style

#171
post #163

Earlier quoted context omitted.

Constness of parameters has zero effect on optimisation. Const declarations may provide a small benefit. Go ahead and apply const as you see fit, but don't expect better code out of it.

Would you guarantee that for every implementation of every C++ compiler ever? Especially considering that constexpr is already there? So, I'd rather leave these clues to the compiler/optimizer.

Constness of pointer or reference parameters has no optimisation effect because it doesn't convey any reliable information. It doesn't indicate whether the thing is written to in the body of the function (the compiler already has that information). It doesn't indicate whether the thing can be written to through another pointer (const parameters may be aliased). And it doesn't indicate whether the thing is written to through the same pointer by a callee (because the language contains const_cast).

Since the very thing that const is supposed to indicate is "no writes", and it doesn't do that, const annotations provide zero information. Thus they add no scope for optimisation.

constexpr isn't relevant to this issue. Since sane programs don't spend any appreciable time calculating constants, it's also rather uninteresting for the purpose of making programs run fast. As far as I can see its only practical purpose is to expand the set of fixed terms allowed as template arguments.

Re: John Carmack's comment on Doom 3's code style

#172
post #10

Earlier quoted context omitted.

The key is to have self documenting code, not undocumented code. If you create functions that do only a single thing, with their purpose fully described by their method signature then you don't need comments - the method itself explains exactly what it does. The author makes a good point that comments are just more text that you need to maintain, and whenever you make changes you now have to make changes in two place…

For me, external documentation is the absolute worst scenario. It takes me at least 4 times longer to read through and understand code without comments explaining in English what's going on. Here's a real-life example: // Toggle between Dropdown and Text if(_protected.fields[field].fieldType() === "Dropdown") { _protected.fields[field].set("fieldType", "Text"); } else { _protected.fields[field].set("fieldType", "Drop…

Rather have code that looks like this:

  toggleDropdown(field) {
   if ...
  }
  
  toggleDropdown(_protected.fields[field]);
Also the external documentation wouldn't have anything like this in it. It would be pretty much:

  UI Code is in XXX. It communicates with ZZZ using YYY. 
  Fields in the UI are changed between text and dropdowns 
  depending on the value in the database that comes 
  from ZZZ. etc.

Re: John Carmack's comment on Doom 3's code style

#173
post #7

Earlier quoted context omitted.

John Carmack wrote a nice article about experiences writing functional code in C++ here: http://www.altdevblogaday.com/2012/04/26/functional-programm...

"a function can still be pure even if it calls impure functions, as long as the side effects don’t escape the outer function" This is a very good point that probably could be systematically exploited. Does anyone know examples of this?

Here's a pretty trivial example in Haskell - computing the factorial function using a mutable variable.

    import Control.Monad.ST
    import Data.STRef

    fact :: Int -> Int
    fact n = runST (fact' n)

    fact' :: Int -> ST s Int
    fact' n = do a  modifySTRef a (*x)) [1..n]
                 readSTRef a
Here the function `fact'` uses mutable variables (encoded in the use of `ST` in its type -- `ST` stands for State Thread) but the function `fact` is pure -- the call to `runST` ensures that none of the side effects leak out of `fact'`.

As with most Haskell code, the types are optional - I included them for clarity.

Re: John Carmack's comment on Doom 3's code style

#174
post #69
post #45

Earlier quoted context omitted.

> A lot of the practices here are enshrined in the Google C++ styleguide: with the notable exception of lining up things horizontally, which tends to be frowned on.

I really do not understand why they chose to do so. In my opinion, it does not improve readability at all, even worse, when the spacing between type and name gets big , your eyes need more work to figure out the correct line relations. Furthermore, this can generate horrible commits, for example: int x; int y; becomes: int x; int y; float z; after adding ONE single variable. But your commit will contain changes for t…

I do use vertical alignment, but that's not necessarily how I'd align the above lines. Assuming y and z are related, but x is not, I'd do them like this:

    int x
    int   y
    float z
I might add a blank line between x and y, too.

If you do vertical alignment such that it emphasises semantic relations, you avoid commits that change more than they should, and it also helps draw your eye to relationships between variables.

Re: John Carmack's comment on Doom 3's code style

#175
post #22

I don't have much experience with C++ codebases, but is this really "exceptional beauty"? The majority of the things he comments on could be enforced with a code-formatter.

It just shows that the author is an intermediate-level programmer at best, if his main concern about the 'beauty' of code is in how it's formatted (spaces vs tabs! K&R vs Allman braces!) and whether it's const correct. Throw in a sprinkle of complaining about silly comments (because obviously there are armies of people out there defending documenting getter/setter functions...) and the obligatory 'templates are bad because I don't understand them', and you have yet another cookie cutter programmer link bait blog post polluting the general internets.

Re: John Carmack's comment on Doom 3's code style

#176
post #78

Earlier quoted context omitted.

Haskell is a special beast, in the sense that it uses single letters a lot for generic types in signatures. Eg: doFoo :: a -> a where doFoo will take any type a and return something of the same type. Due to the density of the language, you'll often find plenty of small, commented functions.

Compare doubleCompose binary_func transformation first_arg second_arg = binary_func (transformation first_arg) (transformation second_arg) vs doubleCompose (b -> b -> c) -> (a -> b) -> a -> a -> c doubleCompose (+) f x y = (f x) + (f y) (also known as the `on` function). There's hardly any good names for x and y, since they can be anything at all.

Your verbose version is just the core of the function, not the type signature. I'm not really sure of what you want to demonstrate here. That short names are good ?

Re: John Carmack's comment on Doom 3's code style

#177

Earlier quoted context omitted.

"a function can still be pure even if it calls impure functions, as long as the side effects don’t escape the outer function" This is a very good point that probably could be systematically exploited. Does anyone know examples of this?

Here's a pretty trivial example in Haskell - computing the factorial function using a mutable variable. import Control.Monad.ST import Data.STRef fact :: Int -> Int fact n = runST (fact' n) fact' :: Int -> ST s Int fact' n = do a modifySTRef a (*x)) [1..n] readSTRef a Here the function `fact'` uses mutable variables (encoded in the use of `ST` in its type -- `ST` stands for State Thread) but the function `fact` is pu…

> As with most Haskell code, the types are optional - I included them for clarity.

I'd just like to make it clear to anyone else reading this. The types aren't optional, but because Haskell has type inference, specifying them is optional.

Re: John Carmack's comment on Doom 3's code style

#178
post #171

Earlier quoted context omitted.

Would you guarantee that for every implementation of every C++ compiler ever? Especially considering that constexpr is already there? So, I'd rather leave these clues to the compiler/optimizer.

Constness of pointer or reference parameters has no optimisation effect because it doesn't convey any reliable information. It doesn't indicate whether the thing is written to in the body of the function (the compiler already has that information). It doesn't indicate whether the thing can be written to through another pointer (const parameters may be aliased). And it doesn't indicate whether the thing is written to…

Would you guarantee that optimizers wouldn't use heuristics at IPO stage?

Either way, you are right, and my comment should have been: 1) constraint 2) providing clues to developers 3) providing clues to optimizer.

Re: John Carmack's comment on Doom 3's code style

#179
post #80
post #63

Earlier quoted context omitted.

I like the last one better. I think it reads more like "assign one of these values to sides[i]" and less like "do one of these three things". "By this latter I mean, if you change the code a little bit, you don't have to rewrite it; it looks basically the same." I'd say that's kind of the point though. The first one would look "basically the same" if in the last else it assigned to sides[j] instead of sides[i]. In th…

My point is that when you are writing complicated production code, and you are a good programmer such that your rate of features successfully implemented is high, then you will often be going to old code and changing that code to behave somewhat differently than it was before. When you do this, you want that old code to be like putty. You want to bend it into a new shape without having to break it and start over. Som…

Note that my response was mainly to the "hard to understand because questionmarks" part of your post. I think that's a pretty weak argument, and that being clear about which part of the code depends on the ifs easily makes up for weird syntax or whatever. The "that is goofy, this is mature" stuff is ridicilous.

I get that there are other reasons for sometimes choosing if-statements instead of if-expressions in cases like this. But then it quickly comes down to a bunch of technicalities (language can't do this, debugger can't do that, ...), and really, even if we're talking C++ stuff only I would not agree with it as general advice.

Re: John Carmack's comment on Doom 3's code style

#180

> C++ code can quickly get unruly and ugly without diligence on the part of the programmers. To see how bad things can get, check out the STL source code. Microsoft's and GCC's[5] STL implementations are probably the ugliest source code I've ever seen. Even when programmers take extreme care to make their template code as readable as possible it's still a complete mess. Take a look at Andrei Alexandrescu's Loki libra…

What's odd is that the C++ community fetishizes these techniques so much. I mean, when faced with problems that require running code at compile team, the lisp community's answer was "ok, just include an interpreter and run your lisp code at compile time with eval-when". The C++ community first insisted that there was never a reason for doing so and then said "ok, but instead of writing code for compile-time execution…

As far as I know, C++ templates were never intended be turing complete. They turned out that way by accident.

If you'd asked the standards body to add a turing complete type-level meta-programming language to C++ in order to generate code at compile time I suspect they'd have told you to get knotted.

What people actually asked for was a reasonable syntax for adding generic functions to C++ that would not carry any runtime cost. Sounds completely reasonable, right? The standards group said "sure, how about this?", and kept adding more perfectly reasonable individual requests to the syntax like template specialisation. Only afterwards did the true nature of crawling horror that they'd inadvertently unleashed become apparent.

Post reply on HN