9 ms·
John Carmack on Functional Programming in C++ (2018)
- jb1991 4y agoThis guy really really knows how to explain things succinctly and expressively.
- montebicyclelo 4y agoI was impressed by his concise daily planfiles. https://github.com/ESWAT/john-carmack-plan-archive/tree/master/by_day https://github.com/ESWAT/john-carmack-plan-archive/tree/mast...
- mi_lk 4y agothanks for sharing, do you know what those three sections (*, + and empty) mean?
- montebicyclelo 4y agoIf I recall correctly, each line is either a feature to add, or a bug to fix. * means it was done on that day. + means it was done on a later day. Empty means either not done or not marked as done. (Tasks may appear in multiple days, and there's no guarantee of tasks being retrospectively marked as complete.)
- actinium226 4y ago> but if you do this with typical C++ containers you will die. Looks left, looks right, quietly closes vscode and rm -rf's the dev folder
- manmal 4y agoJohn‘s pragmatic approach to this topic (any topic really) is very refreshing. Game dev is one of computing world‘s battle grounds when it comes to real-time performance and stability, and there’s just little place for dogma.
- BlueTemplar 4y ago> The pure functional way to append something to a list is to return a completely new copy of the list with the new element at the end, leaving the original list unchanged. Actual functional languages are implemented in ways that make this not as disastrous as it sounds, I'm curious : how do they manage to not make it (performance) disastrous ? Anyone could please forward me some functional-newbie-level examples ?
- massysett 4y agoThe short answer is that with a singly-linked list, you can easily add items to one end (conceptually, typically the front end.) Then if other code has references to the older parts of the list, they’re still valid. A great explanation of this is in “Practical Common Lisp”: https://gigamonkeys.com/book/they-called-it-lisp-for-a-reason-list-processing.html https://gigamonkeys.com/book/they-called-it-lisp-for-a-reaso...
- sidlls 4y agoBut then there are cache locality issues, etc. Algorithmic runtime performance in practice rarely matches the basic big-O analysis taught in CS classes. Case in point: for small cardinality data in practice it’s often (or at least occasionally) faster to use a linear time search algorithm on a fixed length array than to put it, say, in a tree, map, etc. that is big-O “better”.
- zabzonk 4y agobest article i've seen on pragmatic purity - i would only dissent on c++ not providing facilities for pure functions (i don't here mean pure in the virtual=0 sense) - you can, and should, do quite a lot.
- jb1991 4y agoThe language does not offer the features that you were referring to, and that the author of the article mentions. You can write pure functions, but the purity is not checked by the compiler, that is what has been meant here.
- zabzonk 4y agoyes, i know. but there is nothing that stops you from writing pure functions, and quite a lot of features that encourage it, even if they are not checked by the compiler. i'd probably like a c++ compiler that warned me about lack of purity, but as the article says, sometimes you have to bite the bullet.
- mejutoco 4y ago> there is nothing that stops you from writing pure functions If they are not checked, they will be forgotten. When reading a function, if I do not have the guarantee that it is pure, it means the same as an old comment (not much). If the only way to enforce it is convention and force of will it will not be enforced. As an exaggerated illustration: we might add a "without-bugs" annotation to a function, to indicate it has no bugs. After all, nothing stops you from writing bug-free functions :)
- FpUser 4y agoFor example declaring function of the class as const pretty much turns it into pure function and compiler makes sure that it does not modify state of said class. So you are wrong from a practical standpoint. try to modify state of the class inside const function and it'll be compiler error.
- 4y ago
- baby 4y agoHe talked about it a bit during his lex friedman interview. He said he spent some time there, IIRC, and found it somewhat intriguing, perhaps interesting, but needed to get shit done eventually and so stopped his investigation. Very pragmatic. From what I can see in this article he mostly seemed to like the purity aspect of FP. It seems to me like a number of FP concepts aren’t really “FP things”, as Carmack points out you can use these patterns in imperative languages as well. Making your own DSL seems to also often be an FP thing that doesn’t need to be an FP thing. I guess the other way is true as well. OCaml has OOP and support for imperative style (with mutation, early returns via exceptions, etc.)
- bmitc 4y ago> but needed to get shit done eventually and so stopped his investigation. Very pragmatic. That's a strange take by him and on pragmatism. There's also almost nothing pragmatic about C++. Many functional languages are actually quite pragmatic.
- forrestthewoods 4y ago> There's also almost nothing pragmatic about C++. Huh? We’re talking about game dev. C or C++ is the primary language for just about every game engine ever. Even Unity is written C++. I can’t say functional languages haven’t ever shipped a game. I’m sure there’s an example or three. But yeah when it comes to shipping games C++ is the definition of pragmatic.
- bmitc 4y agoThe word game appears twice in all the articles, so I took this writing as a broad outlook. And C++ is not C. Just because C++ ships games doesn't make it pragmatic. Aren't many game codebases considered to be absolute hell? And none of that says anything about the pragmatism of functional-first languages.
- FartyMcFarter 4y agoWhat language would you suggest game developers should have used throughout all these years? It needs to be performant and portable, and it needs to have existed for quite a long time.
- fwlr 4y agoAt least on mobile, it’s not clear where the blogger’s introduction ends and Carmack’s reproduced post begins. 5 years late to mention it, though. Great article.
- Sniffnoy 4y agoIt's not just on mobile; there really doesn't seem to be any visible boundary there.
- jamier1978 4y agoWhen this article was published on his altdev blog it had a big impact on how I wrote code. I really embraced pure functions and tried to think in terms of side effect free code and inputs/outputs. Got no real data for it but I felt the quality of the code I wrote was much better going forward.
- pieterr 4y agoThis should probably have 2012 (instead of 2018) in the title. [1] https://www.gamedeveloper.com/programming/in-depth-functional-programming-in-c- https://www.gamedeveloper.com/programming/in-depth-functiona...
- revskill 4y agoFP to me is more about abstract the functionality, or generalize functionalities. OOP is about abstract the mesaging. When your object is just a pure function, you achive the ultimate abstraction.
- seanalltogether 4y ago> A large fraction of the flaws in software development are due to programmers not fully understanding all the possible states their code may execute in. I think this probably the most important concept for programmers to keep at the front of their minds when working day to day. So many of the problems I see come from developers (myself included) adding new business logic to a process, or fixing a bug and not thinking through the side effects of the change
- HybridCurve 4y agoI actually just started to refactor some of my code earlier this week to deal with this very problem. It was related to deserialized data being transposed from one complex digraph to another. The reason I did not catch it as being problematic sooner was precisely because the state management was loosely distributed within the transposing functions' call hierarchy. The code which was trying to manage the states had emerged from fixes for a series of different problems related to edge cases, and once the new problems started relating to fixes for the others, I had that "Ahhh, well sh*t.." moment you get when finding you were missing something important. > I think this probably the most important concept for programmers to keep at the front of their minds when working day to day. The lesson I had learned was to be more mindful when synthesizing a solution and there might be more state interdependence than we expect. Centralizing state management has helped me solve it in this case.
- jmrobles 4y agoThe problem is even bigger when you are reviewing PRs, the lack of context is the big problem,imo.
- KMag 4y agoI agree. Good code design, regardless of paradigm used, encapsulates complexity in order to minimize the amount of context needed to understand any one bit of code. The better the encapsulation, the better we can scale our limited mental capacity to larger and more complex systems. If the complexity is better encapsulated, it's also faster and easier for the reviewer to acquire the necessary context. When I was first learning OOP, I wish someone had mentioned why classes are useful: they leverage our natural mental abstraction machinery for understanding the world. Walking down the road, we generally think of each car as a whole and ignore thinking about the thousands of parts that make it up. It would be overwhelming to simply cross a busy street if we were considering all of the millions of car components flowing down the streets or considering the various differences in cars all the time, rather than abstractly thinking of various differing collections of parts as just "car". All of this happens at a subconscious level, and we can consciously tap into some of this mental machinery to help us reason about code. Of course, we don't need OOP to leverage this mental machinery, but it's one technique for doing so.
- RichieAHB 4y agoIn terms of applying this to languages other than C++, as someone who’s relatively new (4 years) to Java I expected to see a lot more side effects in the Java I’ve worked with in that time. In fact, the opposite has been true, and much of the points discussed in this article seems to have been adhered to; even going back to some of the older codebases. That said, we mainly have stateless microservices running on k8s, so naturally that leads to a very different mental model than long-lived stateful applications. The thing that really strikes me from the article is the tone in which it’s couched. The pragmatism on display makes it so much easier to read than it could be. Coming from another few years writing Scala and thinking I was a functional programmer (writing hobby projects in Haskell, Elm etc.), I’ve realised I prefer programming in Java in a team (or at least my current workplace’s flavour of Java, and we’re on Java 17); a realisation that still surprises me now. In reality, there’s only so much purity needed to ship even a relatively complex microservice, and Java does that very well with some functional patterns thrown at it.
- zabzonk 4y agoi am by no means a java expert, but i would have thought that the fact that most objects in java are shipped between functions as references makes purity more difficult to effect and to diagnose than in c++ where everything is by default shipped as a value - of course c++ allows you to change this , java less so?
- _old_dude_ 4y agoJava 17 adds records, which are a more blunt tool than using const on method, you have to choose if a class is mutable or not, and not if a method is mutable or not. For microservices, where you deals with data, it's quite effective because it works like typed JSON.
- bspammer 4y agoIn my experience this is mostly solved using the “lombok” library for older versions of Java, and by using the records feature in newer versions of Java. Both of these allow you to create data classes with immutable fields.
- noduerme 4y agoAs a 25+ years coder who's very accustomed to thinking in my own ways, I have a problem with functional-purism similar to the problem I had with globals and factory functions. The fact is that most programs have a lot of states and these states have to be represented somehow. Allowing the objects to contain functions that act on those states is a perfectly good analogy for most business logic or game logic you're modeling. Abstracting what you can abstract into atomic functions is great. But so many functions are more comprehensible when they take place bound to the scope of what it is they're acting upon... again, not to produce an answer, but to change/track/maintain state which is a critical part of what people experience when they interact with a program. I don't think that thinking in terms of objects or even global states should be considered a code smell when what you need to do is to get things done. If/when you need to make some functions synchronous across asynchronous systems, by atomizing their logic, then you will hit upon functional programming methods by default.
- thom 4y agoThe argument isn’t that state is bad or doesn’t need to be managed. It’s more that spreading that state over a large number of areas leads to a lot of complexity and cognitive overhead in terms of expected behaviour at any given point of execution. Functional programming gives you ways to be much more explicit about the transformation being performed and the before/after states (except arguably when you start getting overly cute with zippers and lenses and such). To the extent that this approach hasn’t magically transformed programmer productivity or software quality, we know that both approaches are admissible.
- noduerme 4y agoIsn't encapsulating state - and functions that act on states - within the smallest-possible class ancestor a pretty reasonable way of handling that? I'm not suggesting spaghetti or`GOTO 10400` or `GlobalCatBehaviorFactory.StartMeowing(cat)` or something. I mean we have a paradigm, and it's basically OOP with a dash of functional programming at the functional level. Maybe what I'm trying to say is that a lot of functional programming takes place within the OO model when it's efficient to do so; but functional paradigms just generally ignore state altogether, so absolute purity doesn't leave a route for daily coders writing regular programs to do their jobs.
- optbuild 4y agoWhy aren't more universities teaching functional programming first to form good mental models for the students and then show them when to use state and when to not. Why is everyone teaching Python?
- bick_nyers 4y agoWhen it comes to an Intro. class, they aren't teaching python, they are teaching how computers think, and how to make them think. Python stands the least in the way. Once you get deeper in the program though, I think you need to move away from Python ASAP. Although, if you have any courses that may not be squarely intended for CS majors (like maybe Math/Engineering/Stats/Bio. etc.), then you don't want the language to stand in the way of the concepts there either.
- zactato 4y agoHasn’t Lisp been the language of choice for CS 101 for several decade at MIT and other universities?
- PhDuck 4y agoI think many CS degrees teach FP, I remember learning a variant of SML (mosml) for my first programming course. Now the same university is teaching the same course in F#. Of course a lot of the data science/machine learning is being done in Python, but that is likely due much of the frameworks/tooling around is Python in general in that world.
- jrvarela56 4y agoIt has helped me see classes as a group of functions with parameters in common. The constructor helps me reduce the amount of params passed to each function. If I avoid mutating the internal state across function calls, I'm just using them as a way to group functions together (as a module would). Instantiating and then calling different methods helps me test a process at different points of the executing (testing and debugging). Inheritance helps me clean up stuff that's repeated in specific sub-problems. Mixins/modules help me in a similar way, but for stuff that's reusable in unrelated problems. I'm writing this with Ruby in mind. Seeing how OOP constructs can help me manage code that's written in a functional style has given me a middle ground: I started as 'FP is superior; use everywhere; avoid OOP languages' and now see the OOP stuff just as tools that can make it easier for me to encapsulate and reuse logic. Obv this comes with footguns: taking outside params in methods, local variables changing behaviours, indirection from inheritance/extension - the list goes on and other languages have their own. There's problems with it but as the author says, these things aren't all-or-nothing - so you can nudge the codebase in the FP/'pure' direction. "Objects are a poor man's closure.... Closures are a poor man's object."
- jasmer 4y agoThe psychological aspect of 'purity' in this concept is as interesting as anything. I think that devs are naturally prone to be obsessive compulsive about such things. We can see 'defeating the borrow checker' as a 1st order objective. I think devs fee that 'defeating the borrow checker' gives us much more satisfactiong than 'fulfilling the customers needs'. There's something a big 'chinse finger trap' about FP that consumes our attention on an impusleve level that is not necessarily consistent with what it actually means to be a good engineer. This, along with our will to abstract and to want to use 'shiny new things' etc..
- juunpp 4y agoOn the other hand, there are those who are too stubborn to use anything but a dull knife and a broken hammer. There are situations where you can, e.g., make an error state impossible by defining a couple of types, or using one or two lines of generics. But then, what should have been a simple change becomes an ordeal because the other person lacks the background on all of these ideas. I think all CS curricula should have some sort of class that explores languages like Haskell and Rust, the problems they attempt to solve and how they go about solving them, if only for the intellectual experience. What language you end up using, and what ideas you end up applying, is then up to you on a case-by-case basis.
- jl2718 4y agoI’ve considered a lint rule that requires all functions to be written in Lambda syntax so that closures, side effects, and purity can be explicit. However, I haven’t explored possible effects on compile time, size, and performance. Another possibility is explicit scope closures, like [x,&y]{…} available for every defined scope.