Live data from Hacker News

John Carmack on Functional Programming in C++ (2018)

sevangelatos.com

151–160 of 179 posts

Re: John Carmack on Functional Programming in C++ (2018)

#151
post #13

Earlier quoted context omitted.

> 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.

> There's also almost nothing pragmatic about C++. That's a heck of a weird take. C++'s main value proposition has always been pragmatism. In all possible and conceivable aspects of a programming language. The fact that is a (mostly) superset of C, support for OO, RAII, outstanding backwards compatibility, time-tested and future-proofed, "you pay for what you use", it targets everything and runs everywhere, it's the…

C++ is not necessarily popular for its merits. Inertia has a lot to do with it.

I simply don't consider cans of worms pragmatic.

Re: John Carmack on Functional Programming in C++ (2018)

#152
post #62

Earlier quoted context omitted.

I would say it's often the opposite, unless I'm misunderstanding you. Instead of being encapsulated "deeply" in a class ancestor, state in FP is in the "top" of the program, in the "shallow" part, as explicit as possible and as close to the entry point as possible, while the functional part is the thing that is in the middle. The term for this is "Functional core, imperative shell". In something like Haskell, side-ef…

Um... ok. I think we're sort of both talking about keeping logic unbound from artifacts like display and from objects that contain state. Where I think OO makes sense is that once you admit that objects have properties it makes sense to take the top form of that object and put the functionality there - rather than abstracting it away into some completely other logical place. Maybe this all comes down to what I find e…

[deleted]

Re: John Carmack on Functional Programming in C++ (2018)

#153
post #148
post #124

Earlier quoted context omitted.

The biggest mistake you make is thinking you do something different to me or any other software engineer. You don't. You do the same thing and the same rules apply. You seem to think you're the only person doing anything creative. Well here's a newsflash: it's the same process in games as it is in all other forms of software development. The end result might look different, but the process is the same. The best devs…

>>That is possibly why there hasn't been a big push for FP in games yet, because they haven't yet hit the complexity limit of OO like others have. Are you saying the biggest and probably most complex software (OS-es, browsers, etc.) out there is written in fp languages? >> Games is one of the easiest sectors to be in, the complexity level is low compared to other sectors, especially now all the difficult bits are don…

> Are you saying the biggest and probably most complex software (OS-es, browsers, etc.) out there is written in fp languages?

You know I didn't say that.

The imperative coding approach is cognitively more difficult. So at scale (size of code-base) you either have more bugs and therefore more overhead/cost of development; or you look for coping strategies to minimise the bugs and the cost of development.

Declarative, pure functional programming reduces the cognitive load, because you get to compose things reliably. And because it's declarative and pure you don't have to look under the hood to see what is going on, you know from the surface what large bodies of code do.

A simple example, two functions prototypes:

One in Java

    int foo(string x);
One in Haskell:

    foo :: String -> Int
I can tell you much more about the Haskell one than I can the Java one. The Java one could launch nuclear missiles for all I know, the Haskell one can't and therefore the returned Int has some relationship with the String argument, probably the length of the string. When you don't need to look inside you are more productive. When you can reliably compose code it is more robust and easier to build. When you declare your side-effects up-front, you can make better judgements about what the outcome of calling any function might be.

You are of course free to take whichever approach you like. The imperative one is however just going to get progressively more difficult as applications get more complex. I spent 27 years writing various combinations of assembly, procedural, and OO. I've spent the past 10 using the functional paradigm. If I ever had to go back to the imperative paradigm it would fill me with dread. I still need to use it occasionally for performance optimisations, but I would always wrap it with a declarative interface (where the contract always holds).

> As far as I know those "off the shelf engines" are not written in fp languages. And I would say they are complex.

When I talked about 'easy', what I meant was that games development has much less of the real world to deal with:

* You tend to know your user

* The user doesn't get to have a say

* You often know exactly what hardware you're going to be running on

* You don't have to integrate with 20 other systems by 3rd party providers

* To a certain extent you can put it in a box and move on (I know that's not strictly true, but you're unlikely to be maintaining a game for more than a decade)

* The feature-set of the game won't double every few years as the product competes with others

* Most games have a relatively similar management system

* There aren't massively complex compliance issues or laws that get in the way (yes there are some, but less than other industries)

* Although networking and game-state synchronisation is a hard problem, the scope of distributed computing problems are small

A game engine really isn't that complex. Of course it's hard to make it as optimal as possible, but the problem isn't at the real difficult end of the spectrum. Probably the hardest thing in games now is writing the tooling. I spent a number of years writing core tech and tools for a 3 studio games company and that was just at the beginning of the tooling journey. I assume that's quite a sizeable coding challenge now; one I would definitely us FP for if I was still in the industry.

Re: John Carmack on Functional Programming in C++ (2018)

#154

Earlier quoted context omitted.

What 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.

C99 with code generators written in python or lua for the engine. Implementing templates in the C preprocessor goes pretty badly, but implementing them as a normal language that writes valid C to #include various places works just fine.

> C99 with code generators written in python or lua for the engine.

Nice fantasy, but the real-life codebases that do this make even the most hellish C++ code look like future space-age magic technology.

You're not speaking from real-world pragmatic (heh) experience here, you're just complaining that C++ is hard and that you'd rather go shopping.

Re: John Carmack on Functional Programming in C++ (2018)

#155

> 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 thinkin…

It took me awhile to realize but this is one of the big things that algebraic data types (ADTs) help you to do...design your data types so they have exactly the number of valid states that your domain has. To use another common FP way of saying it...make invalid states unrepresentable.

The notion that your use cases have static well defines use cases is quaint. In practice, I've seen endless debates about whether state ahold be represented as a variable, parameter, type, or member, often with good arguments for all sides.

Re: John Carmack on Functional Programming in C++ (2018)

#156
post #150

Earlier quoted context omitted.

The object state is a parameter passed to the function. The number of arguments passed to a function does not affect it's purity. The feed forward recognition phase of an LLM is pure but has billions of parameters. The purity is defined by it's behavior in relation to again. 1. Does it always return the same result for the same input parameters 2. Does it modify anything other than return a value These two properties…

Not sure why you are arguing about this? The issue is about the idiomatic, expected style that surrounds pure functions and functional programming. The posted article goes into this in greater detail (and more elegantly than this thread) as to why the pattern you describe -- passing a big pointer of potentially large composite data to a function (implicitly or otherwise) -- is not in the spirit of FP purity. If you w…

Not sure why you are arguing about large classes with extraneous data. If this is how you program your classes and it works for you then great but honestly you should try for small immutable class where possible with pure functions ( methods). It's really liberating.

Re: John Carmack on Functional Programming in C++ (2018)

#157
post #151

Earlier quoted context omitted.

> There's also almost nothing pragmatic about C++. That's a heck of a weird take. C++'s main value proposition has always been pragmatism. In all possible and conceivable aspects of a programming language. The fact that is a (mostly) superset of C, support for OO, RAII, outstanding backwards compatibility, time-tested and future-proofed, "you pay for what you use", it targets everything and runs everywhere, it's the…

C++ is not necessarily popular for its merits. Inertia has a lot to do with it. I simply don't consider cans of worms pragmatic.

> C++ is not necessarily popular for its merits. Inertia has a lot to do with it.

This personal assertion says nothing about C++ other than stating you have an irrational dislike for it. There are plenty of reasons that make C++ one of the most successful programming languages in mankind's history, if not the most successful, and inertia is not it.

Re: John Carmack on Functional Programming in C++ (2018)

#158
post #105

Earlier quoted context omitted.

As an ex-3D engine dev, I respectfully disagree. But if you're happy to give some examples of why you can't constrain your data-types then I'm happy to admit I'm wrong. I realise sometimes there's aspects of a game that needs to write to the metal , and compromises need to be made, but that isn't necessary for an entire game. I know people are using my language-ext library with Unity projects too, so I assume there a…

> [1] https://github.com/louthy/language-ext Cool library. I've had a few of these patterns in my Sasa library for years, but you've taken it to the Haskell extreme! Probably further than most C# developers could stomach. ;-) You might be interested in checking out the hash array mapped trie from Sasa [1]. It cleverly exploits the CLR's reified generics to unbox the trie at various levels which ends up saving quite a…

> so it performs almost on par with the mutable dictionary.

I notice you have a comment that says "This trie is now the fastest immutable dictionary I'm aware of". Unfortunately I have to make you aware that my implementation is faster, sorry! ;)

Here's the add-items benchmark:

    BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2)
    AMD Ryzen Threadripper PRO 3995WX 64-Cores, 1 CPU, 128 logical and 64 physical cores
    .NET SDK=6.0.300
      [Host]     : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT AVX2
      DefaultJob : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT AVX2

    |                    Method |      N |           Mean |
    |-------------------------- |------- |---------------:|
    | SysColImmutableDictionary |    100 |      32.315 μs |
    |                  SasaTrie |    100 |       8.007 μs |
    |          SysColDictionary |    100 |       1.440 μs |
    |            LangExtHashMap |    100 |       7.512 μs |
    |-------------------------- |------- |---------------:|
    | SysColImmutableDictionary |   1000 |     524.368 μs |
    |                  SasaTrie |   1000 |     140.537 μs |
    |          SysColDictionary |   1000 |      17.395 μs |
    |            LangExtHashMap |   1000 |     125.663 μs |
    |-------------------------- |------- |---------------:|
    | SysColImmutableDictionary |  10000 |   7,598.532 μs |
    |                  SasaTrie |  10000 |   2,055.220 μs |
    |          SysColDictionary |  10000 |     301.683 μs |
    |            LangExtHashMap |  10000 |   1,842.514 μs |
    |-------------------------- |------- |---------------:|
    | SysColImmutableDictionary | 100000 | 129,811.705 μs |
    |                  SasaTrie | 100000 |  46,752.702 μs |
    |          SysColDictionary | 100000 |   6,103.999 μs |
    |            LangExtHashMap | 100000 |  37,869.597 μs |
* SysColImmutableDictionary = System.Collections.Immutable.ImmutableDictionary

* SysColDictionary = System.Collections.Generic.Dictionary

My other benchmarks are still running, so I won't share them all, but we actually trade blows, your ContainsKey appears to be faster, my collection iteration is twice as fast as yours. I suspect if I removed my struct wrapper it would be a bit quicker, but wouldn't be a real-world fair comparison.

We're both much faster than Microsoft's ImmutableCollections though!

I am certainly interested in what that technique is with the nested NodeWith regards to the 'deriving' question: Yeah, I'm working on a more general solution now, as well as a way to do monad-transformers properly. I've also figured out a way to do infinite tail-recursion for my monadic types, which I'll be deploying in the next major update.

[1] https://michael.steindorfer.name/publications/phd-thesis-eff...

* EDIT *

Here's the full suite of benchmarks, but I've trimmed them down to just the 10,000 item tests and removed the error and deviations for clarity:

    +-----------------------+----------------+
    | Add 10000 items (value type)           |
    +-----------------------+----------------+
    |        SasaTrie       |   2,055.220 μs |
    |        LangExtHashMap |   1,842.514 μs |
    +-----------------------+----------------+
    | Add 10000 items (reference type)       |
    +-----------------------+----------------+
    |              SasaTrie |   2,988.663 μs |
    |        LangExtHashMap |   2,905.552 μs |
    +-----------------------+----------------+
    | ContainsKey (10000 value items map)    |
    +-----------------------+----------------+
    |              SasaTrie |   173,215.0 ns |
    |        LangExtHashMap |   235,577.2 ns |
    +-----------------------+----------------+
    | ContainsKey (10000 reference map)      |
    +-----------------------+----------------+
    |              SasaTrie |     518.789 μs |
    |        LangExtHashMap |     549.952 μs |
    +-----------------------+----------------+
    | Iterate (10000 value items map)        |
    +-----------------------+----------------+
    |              SasaTrie |   457,819.0 ns |
    |        LangExtHashMap |   227,632.7 ns |
    +-----------------------+----------------+
    | Iterate (10000 references map)         |
    +-----------------------+----------------+
    |              SasaTrie |   712,623.1 ns |
    |        LangExtHashMap |   519,250.7 ns |
    +-----------------------+----------------+
    | Random remove (10000 value items map)  |
    +-----------------------+----------------+
    |              SasaTrie | 2,276,772.4 ns |
    |        LangExtHashMap | 1,765,042.9 ns |
    +-----------------------+----------------+
    | Random remove (10000 references map    |
    +-----------------------+----------------+
    |              SasaTrie |   2,946.775 μs |
    |        LangExtHashMap |   2,576.813 μs |
    +-----------------------+----------------+

Re: John Carmack on Functional Programming in C++ (2018)

#159
post #158

Earlier quoted context omitted.

> [1] https://github.com/louthy/language-ext Cool library. I've had a few of these patterns in my Sasa library for years, but you've taken it to the Haskell extreme! Probably further than most C# developers could stomach. ;-) You might be interested in checking out the hash array mapped trie from Sasa [1]. It cleverly exploits the CLR's reified generics to unbox the trie at various levels which ends up saving quite a…

> so it performs almost on par with the mutable dictionary. I notice you have a comment that says "This trie is now the fastest immutable dictionary I'm aware of". Unfortunately I have to make you aware that my implementation is faster, sorry! ;) Here's the add-items benchmark: BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2) AMD Ryzen Threadripper PRO 3995WX 64-Cores, 1 CPU, 128 lo…

[deleted]

Re: John Carmack on Functional Programming in C++ (2018)

#160
post #150

Earlier quoted context omitted.

Not sure why you are arguing about this? The issue is about the idiomatic, expected style that surrounds pure functions and functional programming. The posted article goes into this in greater detail (and more elegantly than this thread) as to why the pattern you describe -- passing a big pointer of potentially large composite data to a function (implicitly or otherwise) -- is not in the spirit of FP purity. If you w…

Not sure why you are arguing about large classes with extraneous data. If this is how you program your classes and it works for you then great but honestly you should try for small immutable class where possible with pure functions ( methods). It's really liberating.

>"It's really liberating"

What IS really liberating is not being stuck to a single paradigm and let person decide which particular tool use to solve particular problem

Post reply on HN