[HN Gopher] Data-oriented design or why you might shoot yourself...
       ___________________________________________________________________
        
       Data-oriented design or why you might shoot yourself in the foot
       with OOP (2009)
        
       Author : tempodox
       Score  : 215 points
       Date   : 2021-06-28 06:28 UTC (16 hours ago)
        
 (HTM) web link (gamesfromwithin.com)
 (TXT) w3m dump (gamesfromwithin.com)
        
       | stephc_int13 wrote:
       | In my opinion, the most important aspect of data-oriented design
       | is to always consider collections instead of so-called "objects".
       | 
       | It is then logical to optimize for access pattern instead of the
       | processing of a single entity.
        
       | talolard wrote:
       | Isn't the middle ground writing custom allocators ? Allocators
       | allocate blocks continuously and developers keep developing with
       | standard OOP?
        
         | PicassoCTs wrote:
         | Imagine if compilers could detect a usescase for custom
         | allocation by flag and speed up the OOP mess
        
         | ratww wrote:
         | How you allocate data is only half of the solution. The other
         | half is also organising access patterns.
         | 
         | In games, for example, DOD requires you to break up a
         | hypothetical _Update_ method into multiple methods that get
         | called at different times (first do all the collision for all
         | objects, then do all movement for all objects, then all
         | rendering, etc). If you skip this step, you get the neatly
         | organised memory but the same random access as before.
         | 
         | Changing the data structure to the way presented in the article
         | without changing the way you access it might even degrade your
         | performance compared to what it was with normal OOP memory
         | organisation.
        
           | meheleventyone wrote:
           | To echo your point which is why ECS isn't data-oriented by
           | default. Merely stuffing component data into flat arrays is
           | not enough. You need as best as possible to have those arrays
           | organised to fit access patterns. Which turns out is quite
           | tricky. For example organising arrays by entity archetype
           | (shape of entity from its components) or some other form of
           | grouping.
        
             | moldavi wrote:
             | I've never heard this, but it seems so obvious now!
             | 
             | Just to be sure, can I get an example?
             | 
             | Is it like: we want to group a spaceship's fuel tank and
             | engine together, because theyll be accessed at the same
             | time?
        
               | meheleventyone wrote:
               | This is going to be a long post. :)
               | 
               | This is partly why a lot of ECS demos have a lot of
               | homogeneous elements (they share all components in
               | common). For example particle systems have long been
               | written in a data oriented manner when running on the
               | CPU. So if you implement it in the ECS style you can just
               | run through the arrays in order and its all good. Or
               | Unity's city sim example. But games tend to have much
               | more heterogeneous entities (they share less or few
               | components in common).
               | 
               | The most obvious example I can think of to dispel the
               | myth of ECS's inherent DoDness is an ECS wherein each
               | component storage is a linked list with each element
               | individually allocated. Even iterating through the
               | homogeneous entity example is likely to be extremely slow
               | in comparison to flat arrays. So there is nothing about
               | the pattern that demands it be implemented in a data-
               | oriented manner.
               | 
               | But back to a more heterogeneous example. I'm going to
               | try to explain it generally because I think a worked
               | version would be enormous and maybe cloud things more?
               | Typically component storage is indexed by the entity ID.
               | You want to look up the component in the storage
               | associated with a particular ID. If all your storages are
               | flat arrays where the entity ID is just an index into the
               | array the more heterogeneous your entities the more gaps
               | you will have to iterate over and correspondingly more
               | memory your game will take up. This isn't great for cache
               | locality or memory usage and we have to iterate over
               | every entity for all systems to find the valid ones.
               | 
               | So the next step uses a dense array and a secondary
               | backing array that is indexed by the entity id. So we can
               | keep our components packed nicely but still look them up
               | easily. Instead of iterating over all the entities for
               | every system we can find the shortest component storage
               | for the set of components the system uses and iterate
               | directly over that and lookup the other components in
               | their storages by the current entity ID. Now we iterate
               | over potentially many fewer entities but essentially do a
               | random lookup into the other component storages for each
               | one. So we're introducing cache misses for the benefit of
               | less things to iterate over.
               | 
               | So what we want is the benefits of blazing through arrays
               | without the downsides of them being pretty sparse and
               | ideally minimizing cache misses. Which is why the concept
               | of an Archetype was invented. If we keep our components
               | in flat arrays but crucially change our storage so we're
               | not keeping flat arrays of every component but keeping
               | separate component storages for each archetype of entity
               | we have right now.
               | 
               | Going from:
               | 
               | AAAAAAAAAA BBBBBBBBBB CCCCCCCCCC
               | 
               | To:
               | 
               | (ABC) A B C
               | 
               | (AB) AAA BBB
               | 
               | (AC) AAAAA CCCCC
               | 
               | (C) CCCCC
               | 
               | If we have a system that just iterates C's it can find
               | all the archetype storages and iterate straight through
               | the C array for them one by one. So ideally we only pay a
               | cache miss when we change archetype, have good cache
               | locality and are iterating the minimum set. Similarly a
               | system that uses components A and C will only iterate the
               | archetype storage of ABC and AC and blaze straight
               | through the A and C arrays of each. Same deal.
               | 
               | This comes at a cost of making adding and removing
               | components from an entity more expensive.
               | 
               | We're also ignoring interacting with other components or
               | the world and how that might work. For example we might
               | want to do damage to another entity entirely. Or we might
               | want to look up the properties of the piece of ground
               | we're stood on. So there is a whole other layer of places
               | we can ruin all this good work by wanting to access stuff
               | pretty randomly. Relationships in games tend to be
               | spatial and stuff tends to move around so it's hard to
               | see a general case solution to the problem.
               | 
               | Then there is other axis to think on like ease of
               | creating the game, how flexible it is to change the game,
               | iteration speed, designer friendliness and so on. Rarely
               | IME has the gameplay code itself been the bottleneck
               | outside of stupid mistakes.
               | 
               | In games this level of optimization is really great when
               | you do have a big mostly homogenous set of things. Then
               | it's well worth the time to structure your data for
               | efficient memory access. City Sims, games like Factorio
               | and so on are classic examples.
        
       | dang wrote:
       | Discussed at the time:
       | 
       |  _Data-Oriented Design (Why You Might Be Shooting Yourself in The
       | Foot With OOP)_ - https://news.ycombinator.com/item?id=1004569 -
       | Dec 2009 (28 comments)
        
       | mytailorisrich wrote:
       | Many of the claimed advantages of "data-oriented design" and
       | especially drawbacks of OOP in this article have nothing to do
       | with data-oriented design or OOP... They are symptoms of bad
       | design.
       | 
       | For instance, good design, and in fact a key concept of OOP, will
       | get you modularity. Especially " _When you write code
       | specifically to transform data, you end up with small functions,
       | with very few dependencies on other parts of the code_ " is the
       | objective of good OO design! Likewise for testing, I don't see
       | how OOP is a problem if you've designed your system well and kept
       | objects nicely encapsulated (likewise a badly-designed system
       | will always be problematic).
       | 
       | Cache utilization is neither here nor there. It all boils down to
       | memory allocation and, again, choice of objects.
       | 
       | Do data-oriented design to work out your dataflow, and then apply
       | OO principles. A key issue is always to choose your objects
       | 'wisely' and a data-oriented analysis will help towards that
       | goal.
        
         | ChrisMarshallNY wrote:
         | This is one reason that I'm always skeptical of dogma. My only
         | tool is a hammer...
         | 
         | But that said, many new techniques (I clearly remember when OOP
         | was the "new paradigm") can offer radical improvements to the
         | status quo.
         | 
         | I use OOP all the time, but I also don't do data processing
         | engines. Most of what I do is GUI and/or device
         | control/communication. For these types of things, OOP is still
         | very much a mainstay, and probably always will be.
         | 
         | I won't sniff at DDD, but get _really_ tired of being lectured
         | about the way I do things; just because it doesn't involve
         | "buzzword _du jour_."
         | 
         | BTW: I remember a guy at a conference, telling me about exactly
         | this problem with OOP, in the late 1980s. That was back when
         | OOP was a relatively new kid on the block, and he was arguing
         | against using it.
         | 
         | He was correct, but it did not apply, in my use case. The
         | massive improvement in complexity management and quality,
         | offered by OOP, far outstripped any speed advantages of classic
         | procedural programming (which is what he was arguing for).
        
           | beigeoak wrote:
           | How much time do you allocate to HN? I see you almost every
           | day in the comments.
        
           | mytailorisrich wrote:
           | > _This is one reason that I'm always skeptical of dogma_
           | 
           | Yes, this is because too often people follow buzzwords
           | instead of trying to understand the concepts.
           | 
           | The issues OO design aims to solve are still valid and still
           | the issue people want to solve. Encapsulation, single
           | responsibility principle, even the concept of object/class
           | (i.e. interacting with data through a set of specific
           | methods) are good design principles, there is no reason to
           | throw them away.
           | 
           | It makes sense to use data-oriented design in applications
           | that are data processing intensive, and this is nothing new,
           | but that is orthogonal with using OO principles.
           | 
           | Likewise, immutable data can have benefits. This does not
           | mean the concepts above are no longer valid. It means using
           | them with immutable data.
        
             | taffer wrote:
             | Personally, I have come to the conclusion that object-level
             | encapsulation is not a good design principle, but rather an
             | antipattern because it complects data with code [1].
             | 
             | OOP tries to manage global mutable state by partitioning
             | and encapsulating it into objects. However, the only way to
             | prevent two unrelated objects from manipulating the same
             | part of the state is to create a tree, i.e., a strict
             | hierarchy between all objects in the system. This
             | combination of data and code in a strict hierarchy means
             | that all data access patterns are baked into this
             | dependency tree, making later, unforeseen changes to the
             | software extremely difficult without taking shortcuts in
             | the dependency tree or having to refactor the entire
             | application.
             | 
             | If, on the other hand, you treat your data as just data,
             | preferably flat and immutable, and keep the code that acts
             | on it separate, then you won't run into this problem. You
             | will be able to change the parts of the code that act on
             | the data structures independently.
             | 
             | [1] See Rich Hickey, Simple Made Easy:
             | https://www.youtube.com/watch?v=oytL881p-nQ
        
               | mytailorisrich wrote:
               | One of the aims of OO design is specifically to make
               | future and unforeseen changes easier by hiding data and
               | enforcing interfaces.
               | 
               | If you treat your data "as just data" with free for all
               | access you revert to the mess that led to the emergence
               | of OO design. This is much worse for maintainability.
               | 
               | > _keep the code that acts on it separate, then you won
               | 't run into this problem. You will be able to change the
               | parts of the code that act on the data structures
               | independently_
               | 
               | That's exactly what should happen with OO (that's one of
               | the purposes of encapsulation).
        
               | lincpa wrote:
               | Are you anti-DB?
        
               | taffer wrote:
               | OO _aims_ at making future and unforeseen changes easier,
               | I just don 't think it achieves its aim.
               | 
               | I am also not sure that a discussion on HN is the best
               | medium to discuss the problem in depth. Nevertheless, I
               | will try to give a practical example:
               | 
               | Suppose you have parts (part_id, description,
               | quantity_on_hand) and suppliers (supplier_id, name).
               | Also, each part is manufactured by multiple suppliers and
               | each supplier manufactures multiple parts. How do you
               | model this? Do you let parts reference suppliers or
               | suppliers reference parts, or do both reference each
               | other? Or do you define a third class PartsSuppliers that
               | manages the references? There is no formal method in OO
               | that tells you what is a sound design choice and what is
               | not. Let's say you chose the latter option
               | (PartsSuppliers) and you need to write a method that
               | computes statistics about the parts. Where do you place
               | this method? You need to add it to PartsSuppliers,
               | because no one else is allowed to have private references
               | to Parts, otherwise you would break PartsSuppliers'
               | encapsulation. No matter what design decisions you make
               | in OOP, you will always have to make a tradeoff between
               | encapsulation of state and extensibility.
        
               | urthor wrote:
               | You're not wrong about what you describe.
               | 
               | But maintaining relational state between things is
               | _really annoying_ in general. You 've mentioned one of
               | the trickiest jobs there is, it's really hard even with
               | purpose built databases.
               | 
               | Doing it in an imperative environment is just _hard_.
        
               | mytailorisrich wrote:
               | > _There is no formal method in OO that tells you what is
               | a sound design choice and what is not._
               | 
               | System architecture is hard. OO design is a set of
               | principles that helps you design a system better by
               | making it easier to maintain and modify. It does not tell
               | you how you should model your objects. To come up with a
               | good model is usually not straightforward.
               | 
               | In fact your example is not an issue specific to OO
               | design. This is a general issue of relationships ('many
               | to many') and there are a number of design principles to
               | help (see database design principles as that's a typical
               | scenario in databases).
               | 
               | > _Where do you place this method? You need to add it to
               | PartsSuppliers, because no one else is allowed to have
               | private references to Partts_
               | 
               | That's not true, but as you say, this is too vast a
               | discussion.
        
               | taffer wrote:
               | I try to put it another way, because it is a much more
               | general problem of OOP: If you have an object A that
               | references an object B and object B references C and A
               | wants to know something about C, we always have to go
               | through B, regardless of whether we are actually
               | interested in B or not. This is because C is part of the
               | private state of B, and if A had a direct reference to C
               | it could mutate C and would therefore break B's
               | encapsulation.
               | 
               | > In fact your example is not an issue specific to OO
               | design.
               | 
               | This is a specific problem with nested data structures.
               | OO design leads to nested data structures to allow
               | encapsulation. The relational answer to this problem
               | would be to break everything up into flat sets of tuples
               | that can be joined as needed, but if everything is just
               | flat data, you can't have encapsulation.
        
               | mytailorisrich wrote:
               | But this is not a good example.
               | 
               | Either this should be modelled so that A can directly
               | reference C to start with, or indeed A has to go through
               | B but can do so to get a reference to C (this does not
               | break encapsulation in itself, it depends on the specific
               | relationships)
               | 
               | It's impossible to avoid nested data structures because
               | these are simply the natural consequence of the system's
               | complexity. For instance, a book is made of sheets,
               | pages, chapters, sentences, illustrations, etc. entities
               | with nested relationships.
        
               | taffer wrote:
               | > Either this should be modelled so that A can directly
               | reference C to start with, or indeed A has to go through
               | B but can do so to get a reference to C (this does not
               | break encapsulation in itself, it depends on the specific
               | relationships)
               | 
               | If A has a reference to C, B cannot, and if B has a
               | reference to C, A cannot. To keep encapsulation intact,
               | references between objects _must_ form a tree. OOP
               | depends on the partitioning of mutable state for
               | maintainability reasons. There is no other way to keep
               | this partitioning intact than to have a tree of objects.
               | 
               | > It's impossible to avoid nested data structures because
               | these are simply the natural consequence of the system's
               | complexity.
               | 
               | This is a purely conceptual view. But you don't have to
               | query it that way at a logical level, or organize it that
               | way at a physical level via memory references. HN
               | comments, for example, are conceptually contained in
               | their parent comments and also conceptually contained in
               | the users who wrote them. In a relational database, on
               | the other hand, the tuples would be contained only in
               | their relations/tables. A query _could_ then associate
               | comments with users at runtime, but comments can be
               | queried on their own, since they are not encapsulated in
               | anything. In OO design, on the other hand, all access
               | paths are baked into the object trees, making later,
               | unforeseen changes to the software extremely difficult
               | without taking shortcuts in the tree and thus destroying
               | the encapsulation.
        
               | mytailorisrich wrote:
               | > _If A has a reference to C, B cannot, and if B has a
               | reference to C, A cannot._
               | 
               | That's not what encapsulation means, and again, it's up
               | to you to come up with a model that makes sense.
               | 
               | > _In a relational database, on the other hand, the
               | tuples would be contained only in their relations
               | /tables. A query could then associate comments with users
               | at runtime, but comments can be queried on their own_
               | 
               | Sure but they are still 'nested' by way of relationship.
               | Of course you don't have to physically nest structures
               | within structures. Both make valid OO implementations.
               | Nothing in OO prevents you from querying comments on
               | their own.
        
               | taffer wrote:
               | > That's not what encapsulation means
               | 
               | Then what does encapsulation mean? If encapsulation means
               | that an object protects all of its private state behind
               | methods, then another object _must_ not be able to
               | manipulate that private state. So if an object A holds a
               | reference to another object B, then B 's state becomes
               | part of A's state and only A must be able to do things
               | that change B.
        
               | kaba0 wrote:
               | OOP only strives to encapsulate _private state_ (though
               | don't forget that too strict rules never make sense. In
               | the end, every design pattern needs an escape hatch). All
               | the methods of the object should modify these in a way
               | that upholds the class invariants. In your example, B can
               | easily be part of the "public state" of the object, or we
               | can make even more gradual distinctions, like only B's
               | identity is relevant for A's state. For example, if A
               | only needs B as optional cache, its modification or even
               | removal will not be a problem.
        
               | ChrisMarshallNY wrote:
               | Agreed, but, for me, I am not really a data programmer. I
               | tend to work in state and identity (classic UI and
               | communications). For these, that characteristic is
               | actually an advantage.
               | 
               | Nowadays, centralized data processing is the big deal for
               | software engineering (as it was, fifty years ago). I'm
               | just a humble app developer, and I'll license stuff that
               | real data programmers do, if I need it.
        
               | urthor wrote:
               | Exactly, the biggest single advantage of OO is the
               | ability to create and manipulate _insanely complicated_
               | data structures simply and easily.
               | 
               | Because it's a way to define a whole bunch of methods AND
               | assign all aspects of thinking about the state of the
               | data to those methods to the author... it's a brilliant
               | tool for designing some insane complexity. Which is
               | exactly what you need for MVC paradigms.
               | 
               | There are multiple tools and they are for different jobs,
               | imagine that.
        
               | baryphonic wrote:
               | > However, the only way to prevent two unrelated objects
               | from manipulating the same part of the state is to create
               | a tree, i.e., a strict hierarchy between all objects in
               | the system.
               | 
               | I see this claim from time to time, and perhaps it's true
               | in typical Java, C++ or even Python, but I don't think
               | I've ever seen anything close to a proof of it. I suspect
               | it is false, since an interface boundary that leaks no
               | state is possible to implement and can give freedom to
               | the designer regarding internal state.
               | 
               | > If, on the other hand, you treat your data as just
               | data, preferably flat and immutable, and keep the code
               | that acts on it separate, then you won't run into this
               | problem. You will be able to change the parts of the code
               | that act on the data structures independently.
               | 
               | This claim is true only insofar as the underlying state
               | being tracked doesn't change much through the lifecycle
               | of the program in development, which is a reasonably good
               | assumption for a game, but not for most other
               | applications.
               | 
               | The problem is that "data" by definition does not capture
               | all of its own invariants. I have personally witnessed
               | long-lived codebases suffer from brittleness when
               | multiple areas of code must read from and manipulate the
               | same underlying data structure. Inevitably, some
               | programmer on the team forgets one of the invariants,
               | since they aren't specified in code (which would make it
               | OO), and then we have a production bug. The solution is
               | then usually to add another "if" statement somewhere. The
               | solution thus makes the code harder to understand and
               | thereby increases the likelihood of this kind of bug
               | related to this particular structure recurring.
        
               | taffer wrote:
               | > [...] an interface boundary that leaks no state is
               | possible to implement and can give freedom to the
               | designer regarding internal state.
               | 
               | Your suspicion is justified. As long as the objects only
               | send immutable messages to each other, encapsulation
               | remains intact. But then you have something closer to an
               | actor system than what people typically think of when
               | they say OOP. Once you pass references to mutable
               | objects, all bets are off.
               | 
               | The ability to specify which states are allowed and which
               | are not, is not a special feature of OOP. In the
               | functional and relational paradigms, there are types and
               | constraints that specify in a declarative way what states
               | should be possible. Types and constraints are enforced by
               | the runtime and are not based on (leaky) encapsulation.
        
               | baryphonic wrote:
               | > But then you have something closer to an actor system
               | than what people typically think of when they say OOP.
               | 
               | This is fair. I tend to think of OO per Alan Kay's
               | description, i.e. message passing, encapsulation and
               | extreme late-binding.[0] That does look closer to actors
               | or even FP than it does the so-called OOP languages,
               | which is what most might think.
               | 
               | > The ability to specify which states are allowed and
               | which are not, is not a special feature of OOP. In the
               | functional and relational paradigms, there are types and
               | constraints that specify in a declarative way what states
               | should be possible.
               | 
               | Types and constraints are still code that are tightly
               | bound to the data they describe (since in a real sense,
               | they are executed, either at compile time or runtime, and
               | the most powerful type systems are Turing complete). The
               | data oriented advocates sometimes forget this when
               | decrying code tightly coupled to data. As types and
               | constraints are added to specify only the proper
               | behavior, the data gains more signal and less noise, and
               | thus becomes more like information and less like data.
               | 
               | [0]http://www.purl.org/stefan_ram/pub/doc_kay_oop_en
        
       | radiospiel wrote:
       | This article was "originally printed in the September 2009 issue
       | of Game Developer." Any news since then?
        
         | jfpardy wrote:
         | There's been a few talks: CppCon 2014: Mike Acton "Data-
         | Oriented Design and C++":
         | https://www.youtube.com/watch?v=rX0ItVEVjHc
         | 
         | CppCon 2018: Stoyan Nikolov "OOP Is Dead, Long Live Data-
         | oriented Design": https://www.youtube.com/watch?v=yy8jQgmhbAU
         | 
         | Building a Data-Oriented Future - Mike Acton [2019]:
         | https://www.youtube.com/watch?v=u8B3j8rqYMw
         | 
         | And a blog/book has been created:
         | https://www.dataorienteddesign.com/
        
         | ratww wrote:
         | Since then, it has definitely become mainstream. For games, it
         | kind of "merged" with Entity-Component-System architecture,
         | which is used by lots of mainstream engines and is kind of
         | popular these days.
         | 
         | IMO, DOD+ECS is not only a good performance hack but also a
         | great architectural pattern for organising game code in
         | general, compared to more traditional techniques.
        
           | worldsayshi wrote:
           | As a react developer I find the OOP prevalence hard to
           | tolerate when I try to learn unity. I'm really hoping that
           | the Dots architecture can make things more enjoyable. I've
           | tried it a bit but there's a lot to learn and from what I
           | understand the APIs are not to be considered stable yet (?).
           | 
           | Meanwhile I've discovered react-three-fiber which feels like
           | the way I want to build 3d stuff.
        
           | meheleventyone wrote:
           | > Since then, it has definitely become mainstream. For games,
           | it kind of "merged" with Entity-Component-System
           | architecture, which is used by lots of mainstream engines and
           | is kind of popular these days.
           | 
           | This isn't really true, it's extremely popular on the
           | Internet but much less so in commercial game development
           | land. Data-oriented design on the other hand is extremely
           | common as things need to run fast. But that's almost
           | exclusively in the underlying engine rather than for gameplay
           | code.
           | 
           | Outside of that there are a lot of in-progress
           | implementations in popular engines (likes DOTS in Unity),
           | lots of very early open-source general game engine projects
           | using them (like Bevy) and loads of open source
           | implementations of which I believe one has actually shipped
           | in a commercial game (EnTT in Minecraft Bedrock Edition). The
           | other famous shipped game using an ECS was Overwatch.
        
           | alpanka wrote:
           | I think it is popular mainly _because_ how it can work in
           | game engine editors (unity, UE, ...).
           | 
           | You can't really do much OOP from the graphical editor but
           | ECS is basically drag and drop
        
           | bregma wrote:
           | For most game developers in the scene these days, DoD + ECS
           | _is_ the traditional technique. The dogma also requires
           | chanting in unison how much better than OOP it is.
        
       | tabtab wrote:
       | Somewhat related: it's time to _toss out file trees_ as our
       | primary module management technique:
       | 
       | https://news.ycombinator.com/item?id=25347043
       | 
       | We've outgrown file trees.
        
       | freddealmeida wrote:
       | I always hated OOP. thank god for this.
        
         | menotyou wrote:
         | Me too. Lot's of superfluous concepts called "abstractions" to
         | make simple things complex.
         | 
         | Interestingly all discussions about other paradigms on HN end
         | very fast in "Oh, I can solve this somehow with [put in your
         | favorite design pattern in here]" (Big deal, both paradigms are
         | obviously turing complete). "Design Pattern" was for me always
         | short for "Complex workaround for a problem you would not have
         | if you wouldn't use object oriented paradigm".
         | 
         | But more generally, OOP is for developers whose mental model of
         | the world is categorizing things into object hierarchies. For
         | them it is the most intuitive approach to model the world.
         | 
         | For me this is as counter-intuitive as it can be. My mental
         | model of the world just does not work like that.
        
       | SonOfThePlower wrote:
       | Looks like Fortran programmers have been doing data-oriented
       | design since forever.
        
       | ferdowsi wrote:
       | In all my experience with OOP, it's always been inheritance that
       | is the root of all evil. Rust and Go got this correct by having
       | class-like objects with no inheritance, to achieve encapsulation
       | without fragility.
       | 
       | Unfortunately, all the other languages that included inheritance
       | in their design can't wish it away. Devs are going to keep
       | reaching for inheritance as the closest, most comfortable
       | abstraction.
        
         | meheleventyone wrote:
         | The weirdest thing is that the ECS as a way of building a game
         | is inherently object oriented. You take a set of components and
         | compose an object called an entity. The components on the
         | entity define not only it's data but also it's behavior by the
         | set of systems that act on the corresponding components. And
         | you can take these object definitions and inherit them to add
         | additional behavior or change the existing behavior by adding
         | more components to the new definition.
         | 
         | Then if you solve the entity communication conundrum with
         | message passing and don't allow entities to directly access one
         | another's data you basically have all the elements.
        
           | Bekwnn wrote:
           | > The components on the entity define not only it's data but
           | also it's behavior by the set of systems that act on the
           | corresponding components. And you can take these object
           | definitions and inherit them to add additional behavior or
           | change the existing behavior by adding more components to the
           | new definition.
           | 
           | This seems to miss what ECS actually is, unless you're just
           | referring to the old-school way of doing entity components
           | and not the data-oriented way.
           | 
           | Data-oriented ECS way of doing things is to separate state
           | and behaviour. Entity components essentially become structs
           | where their only behaviour is potentially some getter/setter
           | utilities.
           | 
           | Behaviours are then state-less systems (just functions,
           | essentially) which act on a set of components.
           | 
           | For example, a PhysicsUpdateBehaviour might take in a
           | RigidBodyComponent and a HealthComponent to perform a physics
           | update and apply physics/fall based damage.
           | 
           | The main benefit of ECS (imo) isn't even really performance.
           | It makes code in complicated game projects much easier to
           | manage by clarifying the game loop and by making it much more
           | obvious how and when entity state is being modified.
           | 
           | It's the kind of thing that potentially complicates a smaller
           | project, but makes larger more complex projects easier to
           | manage.
           | 
           | This Overwatch GDC talk is the best breakdown/example of
           | data-oriented ECS in a AAA game that I know of:
           | https://www.youtube.com/watch?v=W3aieHjyNvw
        
             | meheleventyone wrote:
             | I know what an ECS is. Components are decoupled from
             | systems (not not visa versa) but the actual behavior of an
             | entity is defined by the set of systems that run on the set
             | of components so in that sense the set of components
             | defined what the Entity is including it's behavior. An
             | Entity is defined in terms of it's data and it's data
             | brings along behavior.
        
           | adamdusty wrote:
           | Sure ECS is object oriented in the same way C99 is. Yeah,
           | technically you are building up some OOP functionality, the
           | same way you emulate constructors and instance methods in C
           | by making functions to init data structures and functions
           | that take references to a struct to modify it's data. That
           | doesn't make C object oriented.
           | 
           | In ECS you are decoupling data from behavior, which is
           | basically the entire paradigm of languages like rust and go.
           | You could argue that by defining systems in a way that they
           | run on certain components you are defining behavior and data
           | in one, but I think that's a stretch.
           | 
           | It clearly differs from OOP when I have 2 entities with
           | components that have overlapping and non-overlapping systems.
           | If e1 has components c1 and c2, and e2 has components c2 and
           | c3, and c1 and c2 are used in system s1 while c2 and c3 are
           | used in system s2, I don't see how you would model that with
           | OOP without adding data to classes that don't need it. In OOP
           | both e1 and e2 would need all the logic from s1 and s2, or
           | needlessly specialized versions of s1 and s2. Which would be
           | solved via inheritance (either class based or interface
           | based).
           | 
           | In ECS your data exists in an array of components and any
           | part of your program can operate on any component however it
           | wants. I've never needed message passing for anything I've
           | worked on.
           | 
           | That's not to mention that the main benefits of ECS have
           | nothing to do with language paradigm. ECS main advantage is
           | cache coherency and easier parallelism.
        
             | meheleventyone wrote:
             | You're too worried about the underlying implementation.
             | Think a bit more about the runtime expression in terms of
             | the resulting Entities and how the set of components linked
             | to them defines data and behavior and what you could do
             | conceptually to extend that.
             | 
             | > That's not to mention that the main benefits of ECS have
             | nothing to do with language paradigm. ECS main advantage is
             | cache coherency and easier parallelism.
             | 
             | ECS is not data-oriented by default. :)
             | 
             | I have a long post explaining things in this thread here:
             | https://news.ycombinator.com/item?id=27663218
        
               | adamdusty wrote:
               | My comment has nothing to do with implementation. We're
               | talking about ECS in the context of DoD so I'm not sure
               | what relevance being data-oriented by default has. It
               | seems like you're just confusing the concepts of ECS,
               | DoD, and composition.
               | 
               | Your entire post on ECS is, "If you don't use DoD with
               | ECS than you're not using DoD". Well yeah.. obviously? If
               | you implement an ECS and then use it without data-
               | oriented structures, then yes obviously you don't have
               | data-oriented design.
               | 
               | You're creating a strawman. You're saying if you take
               | ECS, remove the idea of storing components independently
               | of entities, and pass them in an inefficient manner to
               | systems, then you don't have DoD. ECS isn't inherently
               | DoD, literally nothing is. Arrays aren't inherently cache
               | friendly. There's nothing stopping you from making a
               | language that allocates data randomly throughout reserved
               | memory and every array element points to each location.
               | No one is arguing ECS is inherently DoD, but it is a good
               | design to facilitate DoD.
               | 
               | > For example we might want to do damage to another
               | entity entirely.
               | 
               | Add a damaged component to the entity to damage. Consume
               | damage component in a system.
               | 
               | > Or we might want to look up the properties of the piece
               | of ground we're stood on
               | 
               | Use a position component on the entity standing on the
               | ground. Consume the position in a system and look at the
               | properties of the terrain map at that position. Even
               | simpler for grid based maps.
               | 
               | > We're also ignoring interacting with other components
               | or the world and how that might work
               | 
               | You interact with other components by defining
               | interactions in systems based on those components.
               | 
               | You've created an ECS in a way that doesn't take
               | advantage of any of the benefits, and complaining that
               | all you're left with is the disadvantages.
        
               | meheleventyone wrote:
               | The approach I talk about with archetypes is the one used
               | by Unity and many open source ECS implementations. It's a
               | pretty standard way to solve the issue.
        
           | colllectorof wrote:
           | ECS (which I have not used) sounds a lot like Traits. The
           | name and core concepts for Traits were defined in 2003 in an
           | ECOOP paper [1]. I think traits were first implemented by
           | Squeak Smalltalk in 2005.
           | 
           | [1] http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
        
             | meheleventyone wrote:
             | Very similar although in an ECS the relationship is
             | backwards, Entities get behaviour based on what data they
             | contain rather than getting behaviour from traits and
             | needing to add state to make them work.
             | 
             | The ECS approach can lead to some confusing things like
             | adding a component to an Entity and having strange
             | behaviour result as a system the programmer didn't expect
             | to be triggered is run. This can lead to systems having
             | quite complex definitions based not just on the components
             | the system needs to run but also on the components that
             | shouldn't be present and so on.
        
           | professoretc wrote:
           | ECS is really just OOP with _dynamic_ multiple inheritance:
           | an object can inherit from multiple base  "classes" (with
           | "components" providing the data and "systems" providing the
           | code) and this inheritance structure can be changed at
           | runtime, by adding/removing components. Everything else
           | (struct-of-arrays vs. array-of-structs) is just low-level
           | implementation details.
           | 
           | When I implemented a variation on ECS for a game I'm
           | building, I did exactly as you suggest, re, message passing:
           | components receive and respond to _messages_ but their
           | implementation is hidden.
        
             | ratww wrote:
             | _> ECS is really just OOP with dynamic multiple
             | inheritance_
             | 
             | It's composition, not inheritance.
        
               | professoretc wrote:
               | Technically, it's neither. Unless your programming
               | language _directly_ supports ECS, you 're not going to be
               | implementing the relationship between entities and
               | components as _either_ proper inheritance or a collection
               | of data members, because neither of those can be changed
               | dynamically.
        
             | klodolph wrote:
             | Inheritance is one way of describing it but I don't think
             | the term really fits. An object is composed of multiple
             | components, and the composition can be changed at runtime.
             | Saying that the object inherits from multiple base
             | "classes" seems like it just makes the concept less clear.
             | 
             | Some languages have class-based systems with inheritance:
             | one class inherits from another, and methods implemented in
             | the superclass can be used in the subclass. Some languages
             | have prototype-based systems with inheritance: one object
             | inherits from another, and methods implemented in the
             | prototype can be used in the object.
             | 
             | Component-based systems don't really fit my mental model of
             | inheritance here.
        
           | dkersten wrote:
           | > You take a set of components and compose an object called
           | an entity.
           | 
           | That's an overly broad definition of "object", since under
           | that same definition a record type (C struct) or any other
           | blob of memory is an object.
           | 
           | In the common type of "components only store data" ECS, the
           | entity is an ID (think a foreign key) that connects multiple
           | records together and systems are independent functions (they
           | are not tied to nor live in an entity) that operate on
           | collections of subsets of these components.
           | 
           | That sounds a lot more like old school C-like procedural
           | programming to me than it does like OOP. There's more to OOP
           | than the data attributes a class contains (eg the associated
           | methods)
           | 
           | I suppose it depends on your game engine and your ECS, but
           | since entities don't contain logic, it's the systems that
           | communicate between each other (either by sending messages or
           | by accessing the other entities components or by just calling
           | functions of other systems). This isn't all that different
           | from different parts of a procedural program communicating.
           | Although I do personally think that making a system be an OOP
           | object does makes sense, but it doesn't have to be.
           | 
           | With that said, it seems pretty common in games to use a
           | component system that isn't "pure ECS" (like the default
           | Unity components prior to their new ECS), which definitely
           | seems like typical OOP to me, just decomposed a bit more.
        
             | meheleventyone wrote:
             | > That's an overly broad definition of "object", since
             | under that same definition a record type (C struct) or any
             | other blob of memory is an object.
             | 
             | I think that's because you seem to have stopped at the
             | second sentence the rest is important as well. I'm also
             | talking about a level above the ECS implementation. What is
             | the running thing actually doing.
             | 
             | > With that said, it seems pretty common in games to use a
             | component system that isn't "pure ECS" (like the default
             | Unity components prior to their new ECS), which definitely
             | seems like typical OOP to me, just decomposed a bit more.
             | 
             | Yes this also models much the same thing at runtime.
        
               | dkersten wrote:
               | How is the running thing operating on components any
               | different from functions in a purely procedural language
               | like C operating on records/structs?
               | 
               | > Yes this also models much the same thing at runtime.
               | 
               | In a different way, though. It also generally misses out
               | on the data-oriented benefits of an ECS.
        
               | meheleventyone wrote:
               | It's the conceptual organization. The Entity is defined
               | by data (components) that bring along behavior (systems).
               | So an entity executing at runtime (say you're making
               | Pacman and it's the Red Ghost) is an object and is
               | defined by the combination of data and behavior.
               | 
               | The underlying implementation is irrelevant basically.
               | You could implement the ECS in an OOP style and the same
               | it true. You could do it in a functional style and it
               | would be true. You could do it in straight bytecode for
               | some obscure hobby VM and it would be true.
        
               | dkersten wrote:
               | Unlike traditional OOP, the data and behavior are
               | decoupled though. Similar to data and functions.
               | 
               | That is, you can add components that don't get operated
               | on by any particular systems because the entity doesn't
               | have the other prerequisite components and you can have
               | systems that don't operate on the components. You can
               | have many systems operate on one particular component and
               | many components operated on by a system.
               | 
               | In OOP, the data and the operations are packaged to
               | whether as one. You also typically have encapsulation and
               | it's considered bad practice for one class to operate on
               | another classes data directly.
               | 
               | It seems that both models achieve similar things, but
               | they're far from the same thing. Just like how procedural
               | or functional programming achieve similar things to OOP,
               | and you can do OOP in these paradigms or these paradigms
               | in OOP. There's a lot of cross over, but that doesn't
               | make them all the same thing.
               | 
               | If anything, I'd say that ECS are a relational model but
               | with a very limited query system compared to something
               | like SQL.
        
               | meheleventyone wrote:
               | > Unlike traditional OOP, the data and behavior are
               | decoupled though. Similar to data and functions.
               | 
               | Except the data and behavior aren't decoupled. The
               | components are decoupled from the systems, but the
               | systems are still very much dependent on the components.
               | Just like a method is usually dependent on the instances
               | data or a function is dependent on the data passed in.
               | 
               | > That is, you can add components that don't get operated
               | on by any particular systems because the entity doesn't
               | have the other prerequisite components and you can have
               | systems that don't operate on the components. You can
               | have many systems operate on one particular component and
               | many components operated on by a system.
               | 
               | You can have a member that isn't operated on by any
               | methods and methods that don't operate on members.
               | 
               | At the level your talking about there isn't much
               | difference between a function and a method. It's mostly
               | syntax.
               | 
               | method(instancedata);
               | 
               | verses
               | 
               | instancedata.method();
               | 
               | Really we're getting caught up in implementation details
               | because a class definition isn't the be all of how to
               | define an object. There is really no reason we couldn't
               | define objects in a programming language through
               | composition.
               | 
               | ECS very much is a relational model and you're right it's
               | very limited in comparison to things like SQL because
               | it's trying to model something very simple. Game Objects!
               | The relations defined are exactly what brings data and
               | behavior together under to create the runtime object we
               | call an Entity under the pattern conventions.
        
               | dkersten wrote:
               | > Just like a method is usually dependent on the
               | instances data or a function is dependent on the data
               | passed in.
               | 
               | Just like a C function operating on a C struct. So, what,
               | in your opinion, is the difference between procedural
               | programming and OOP?
               | 
               | > It's mostly syntax.
               | 
               | Which is why I think there is more to OOP than a classes
               | attributes and it's methods. There is also inheritance,
               | encapsulation levels, the fact that an objects identity
               | is its attributes (the object is its data, an entity has
               | its components but is separate from them), the fact that
               | an object is a singular thing which it's methods operate
               | on (as opposed to how systems operate on collections of
               | components, imagine a class system where a method
               | operated on all instances of that class!).
               | 
               | Sure at the end of the day it's all the same and we're
               | just arguing semantics, but that was my point and what I
               | lead with: it's an overly broad definition. If
               | definitions are too broad then they really don't add any
               | value, but I believe a distinction between OOP and ECS is
               | useful because they are used in different ways.
               | 
               | But fundamentally I don't disagree, I even once wrote a
               | blog post about how all of the OOP principles exist in an
               | ECS! I just don't believe that thinking of them as
               | slightly different implantations of OOP is useful because
               | of how their properties differ.
        
               | meheleventyone wrote:
               | I actually wrote way back at the start about
               | encapsulation and inheritance (along with message
               | passing). So I'm not sure my definition really is overly
               | broad.
               | 
               | I'm also mostly talking about the runtime consequences of
               | the things that most people worry about at the time of
               | programming.
               | 
               | But thanks for making me defend my thought!
        
           | Zababa wrote:
           | From a distant point of view, everything is OOP. You can
           | treat anything like a black box that you push button on to
           | make things. You push things on your keyboard, without
           | knowing how it works. Your keyboards activate things on your
           | computer, without knowing how it works. The computer ask the
           | screen to update with the new date, without knowing how it
           | works.
           | 
           | From a distant point of view, everything is data oriented.
           | Your thought are transformed into keyboards presses by the
           | keyboards, that are transformed into events by your computer,
           | that is transformed into what you see by your screen.
           | 
           | I could do the same with a frozen pizza factory: you can see
           | the ingredients flow in the machines (functions), or you can
           | see the different machines passing things to others like
           | objects. The problem is that then the classification between
           | "OOP" and "non-OOP" doesn't mean anything anymore and is now
           | useless.
        
         | Zababa wrote:
         | > In all my experience with OOP, it's always been inheritance
         | that is the root of all evil.
         | 
         | I have this "theory" in the back of my head that trees are
         | usually the wrong things to model thing in life but it's what
         | come to us naturally. For example, a blog with categories and
         | sub-catogories for articles (a tree, inheritence) can often
         | describe the content better by using tags (a graph,
         | composition). I think that's because trees are easy to deal
         | with and understand, but graphs are more "open" with what you
         | can do.
        
           | hcarvalhoalves wrote:
           | Data modelling in OOP is an exercise in coming up with
           | Platonic ideals, resulting in a hierarchical (tree-like)
           | ontology as you try to choose of the atributes as the
           | categorisation dimension, and leaving everything else as
           | properties.                   class Animal         class
           | Mammal inherits Animal         class Feline inherits Mammal
           | class Cat inherits Feline         ...
           | 
           | This is different than just asserting facts with data, which
           | can lie in multiple dimensions.                   Is Feline
           | Is Mammal         Is Fluffy         Is White         Does
           | Meow
           | 
           | The later is a much more flexible data model as it more
           | closely mimics observed (subjective) reality, and is less
           | disturbed when a new (counter-)example is introduced, but is
           | also harder to reason about than idealised categories.
        
             | inopinatus wrote:
             | Uh, no. Either of those are possible without leaving the OO
             | paradigm, and only very poorly taught and inexperienced
             | students model data as an object inheritance hierarchy.
        
               | coldtea wrote:
               | If only.
               | 
               | Spoken like someone who has never seen any kind of
               | representative sample subset of real world code...
        
             | throwaway894345 wrote:
             | To your point, there are programs that deal in
             | _ontologies_. These are the only times that it makes sense
             | to care about the relationship between things. For example,
             | an ontology might have a concept of a city and it might
             | know that Munich is a city. But this is all _data_ , it
             | isn't about "types". It never makes sense to write `class
             | Munich extends City {}` for the purpose of your program.
             | Rather, you might have:                   struct Entity {
             | name: string,             parent: Option<Entity>,         }
             | let city = Entity { name: "city", parent: None };
             | let munich = Entity { name: "Munich", parent: Some(city) };
             | 
             | That said, if you really wanted to make life hard for
             | yourself, you could use types _as_ data provided your
             | language has a runtime type system and reflection (you
             | could dynamically generate `class City` and `class Munich
             | extends City` when deserializing `[{name:  "city", parent:
             | null}, {name: "Munich", parent: "city"}]` or something).
             | But this is the kind of Rube-Goldberg territory that
             | "Kingdom of Nouns" thinking leads us toward.
        
             | wwweston wrote:
             | Hence traits?
        
               | beaconstudios wrote:
               | And ECS is fundamentally a trait system!
        
               | dkersten wrote:
               | I know all the terms are overloaded nowadays so
               | everything's kinda unclear but I always wished that ECS
               | components had been called traits, because adding a
               | component to an entity gives it some a trait like "this
               | thing has a position in the world" or "this thing can be
               | drawn" (and perhaps have systems named "behaviors",
               | because systems add behavior to entities based on the
               | traits/components that they have)
               | 
               | Years ago, when ECS was just starting to be talked about
               | (after Adam Matrins blog posts), I wrote a toy ECS where
               | I used that naming convention. Nowadays I stick to the
               | mainstream terminology since that's what other people
               | know.
        
               | pjmlp wrote:
               | They have, but there is a certain tendency to ignore CS
               | literature.
               | 
               | "Component Software: Beyond Object-Oriented Programming"
               | 
               | https://www.amazon.com/Component-Software-Beyond-Object-
               | Orie...
               | 
               | 1st edition, 1998
        
               | beaconstudios wrote:
               | Yeah I've commented before that "entity component system"
               | is roughly synonymous with "thing piece thing" or
               | something like that - it's a really bad name because it's
               | so ambiguous. Anything including the term trait would be
               | 1000x better because at least "trait" means something.
        
               | bcrosby95 wrote:
               | I think it's named ECS because before then
               | entity+component designs were common in games. ECS
               | generally took the behavior off the C and puts it in the
               | S.
        
           | firethief wrote:
           | You might enjoy this essay from 1965, "A City is Not a Tree":
           | https://www.patternlanguage.com/archive/cityisnotatree.html
        
           | necheffa wrote:
           | Except that trees are by definition graphs with specific
           | conditions on direction and cycles.
        
             | db48x wrote:
             | Perhaps it is those very conditions which make trees less
             | useful than they at first appear.
        
               | firethief wrote:
               | I would argue that its those restrictions that make trees
               | a useful simplification, but also a simplification
        
               | potatoz2 wrote:
               | In general yes, but in certain case they force you to
               | simplify in a way that's later painful.
        
           | jhgb wrote:
           | > I have this "theory" in the back of my head that trees are
           | usually the wrong things to model thing in life but it's what
           | come to us naturally.
           | 
           | Have you by any chance read the relevant passages in SICP? It
           | has some things to say about OOP ontologies.
        
             | ferdowsi wrote:
             | Could you link this?
        
               | Jtsummers wrote:
               | https://mitpress.mit.edu/sites/default/files/sicp/full-
               | text/...
               | 
               | Possibly that section. It's not about OOP specifically,
               | but about type hierarchies generally.
        
             | Zababa wrote:
             | I don't think I did, I didn't finish the first chapter of
             | SICP. I'm sure that I'm not the first one to come up with
             | this though.
        
           | oftenwrong wrote:
           | There's a great old (2005) blog post on this topic:
           | 
           | Clay Shirky - _Ontology is Overrated: Categories, Links, and
           | Tags_
           | 
           | https://web.archive.org/web/20191117162526/http://shirky.com.
           | ..
        
             | state_less wrote:
             | Folks may enjoy some of these old posts too.
             | 
             | https://en.m.wikipedia.org/wiki/Pyrrhonism
             | 
             | For example, putting regions under a cold climates
             | category, might not make sense to someone living near the
             | pole where they would consider the same regions to be warm
             | climates.
        
             | Zababa wrote:
             | Thanks, that was a great read. Tangentially related, but I
             | wonder what's the impact of Windows having a really bad
             | search. Maybe people are thus relying more on the folder
             | hierarchies, and that influences how they think?
        
           | dexwiz wrote:
           | Humans have a natural tendency to refine a single idea by
           | splitting it into two based on a differentiating factor. This
           | ends up looking like a tree when applied repeatedly.
           | Dichotomous keys for species identification are another
           | example of this.
        
             | eska wrote:
             | The problem being that they don't categorize things into
             | one tree, but many. One can view the same thing in
             | different ways. OOP tree hierarchies do not allow that.
             | 
             | It's like trying to categorize your photos in a directory
             | tree. Do you categorize by year first, by person, or
             | location? There is no correct answer. What people want
             | instead is a photo album with tags. The same problem
             | applies to OOP.
        
               | Zababa wrote:
               | > It's like trying to categorize your photos in a
               | directory tree
               | 
               | A problem that made me think about tags instead of
               | categories was precisely that: I have photos that I want
               | to organize. I started by organizing them by person with
               | a folder for each person. But how do I handle a photo
               | where multiple people are in it? Tags don't have this
               | problem. Unfortunately file systems don't support tags.
        
               | setr wrote:
               | > What people want instead is a photo album with tags
               | 
               | What you really want is hierarchal tags :)
        
               | splistud wrote:
               | No, no. We want tags. Later we want hierarchal tags (with
               | some complex workarounds to maintain some legacy thing
               | included in the implementation).
        
           | prionassembly wrote:
           | Oh man, I have a rabbit hole for you.
           | 
           | https://en.wikipedia.org/wiki/Rhizome_(philosophy)
        
             | steveklabnik wrote:
             | Here's a near-decade old talk from me on this exact topic:
             | https://www.youtube.com/watch?v=YfKAScYkGlk
             | 
             | I haven't watched this in like... a long time, so maybe I'd
             | think it's bad now.
        
           | duped wrote:
           | A tree is a graph where all vertices have exactly one path
           | through them.
           | 
           | Trees are often implemented using composition, and
           | inheritance can be graphical (the Diamond Problem is not game
           | over).
        
             | jstanley wrote:
             | A        / \       B   C      / \ / \      D E F G
             | 
             | This is clearly a tree, but doesn't B have 2 paths through
             | it? A->B->D and A->B->E.
        
               | gmfawcett wrote:
               | You're right. The correct definition is that any two
               | vertices are connected by (i.e. are the endpoints of)
               | exactly one path.
               | 
               | Fun fact -- since graph-theory trees are undirected by
               | definition, an inheritance graph is more properly called
               | an arborescence (for single inheritance). For multiple
               | inheritance it's a DAG (with diamond-pattern) or a
               | directed tree (without).
        
         | colllectorof wrote:
         | _> In all my experience with OOP, it's always been inheritance
         | that is the root of all evil._
         | 
         | It's not, though, and the fact that people keep repeating this
         | meme shows that most developers don't even bother thinking
         | about issues they face beyond superficial blamesplaining.
         | 
         | The reason inheritance causes so many issues in languages like
         | Java is because they are statically typed and also _use classes
         | as types_ [1]. Classes must be somewhere in the inheritance
         | tree, hence you are forced into some place of that tree. To
         | make things worse, Java has many keywords that restrict what
         | inheritor of a class can do (private, final, etc).
         | 
         | Inheritance is much less troublesome in, say, Smalltalk, since
         | the language is dynamically typed. If someone expects you to
         | implement Foo, you can (almost always) just implement its
         | relevant methods without explicitly extending the class. Thus,
         | a whole host of annoying scenarios simply does not occur.
         | 
         | --
         | 
         | [1] BTW, this breaks one of the fundamental commandments of
         | classic OOP: you should not depend on implementation details of
         | an object, only on its message protocol. Obviously, it's
         | impossible to be independent of implementation details if some
         | library _forces you_ to use a particular class.
        
           | SPBS wrote:
           | > Inheritance is much less troublesome in, say, Smalltalk,
           | since the language is dynamically typed. If someone expects
           | you to implement Foo, you can (almost always) just implement
           | its relevant methods without explicitly extending the class.
           | 
           | Sorry, I don't understand this sentence. Isn't inheritance
           | simply a way to avoid writing duplicate code? If you write
           | the code to implement methods, isn't that not inheritance
           | anymore?
        
             | ratww wrote:
             | What the grandparent post means is that in dynamic
             | languages you can just implement one of the "base" methods
             | yourself instead of inheriting from a class that's bigger
             | than you need, in order to avoid problems. I personally
             | don't have an opinion on that, but it's not something I'd
             | do myself.
             | 
             | Also, like the sibling said, inheritance is a tool that
             | does multiple things: code reuse, which we call
             | _implementation inheritance_ , being the one everyone hates
             | (the age-old advice is to use composition for code reuse
             | instead), and _interface inheritance_ being the one
             | everyone loves.
        
             | throwaway894345 wrote:
             | Inheritance conflates code reuse ("avoid writing duplicate
             | code") with polymorphism (allowing for multiple different
             | instances to implement the same interface). It also allows
             | for trampolining method calls up and down a hierarchy (a
             | method in a base class might call another method which
             | might be overridden by another class in the hierarchy).
             | 
             | Outside of OOP, we use composition for reuse and interfaces
             | for polymorphism, and we don't trampoline method calls up
             | and down a hierarchy because it's (probably?) always a bad
             | idea. When we really need reuse and polymorphism, we can
             | use both composition _and_ interfaces, since the two are
             | correctly orthogonal.
        
               | [deleted]
        
           | throwaway894345 wrote:
           | > It's not, though, and the fact that people keep repeating
           | this meme shows that most developers don't even bother
           | thinking about issues they face beyond superficial
           | blamesplaining.
           | 
           | I don't know that I'd say "inheritance is the _root_ of all
           | evil " (there are lots of antipatterns in OOP that are
           | unrelated to inheritance, like Joe Armstrong's banana-
           | gorilla-jungle observation) but I will say that inheritance
           | is pretty close to useless in the best case and harmful in
           | most cases. And I say this as someone who learned to program
           | and then became a professional programmer when OOP was all
           | the rage. I was taught OOP without the previous bias of other
           | paradigms; it was only after learning other paradigms that I
           | was able to articulate frustrations I was having with OOP.
           | The implication that people who criticize inheritance in this
           | way "haven't bothered to think" is patently false in the best
           | case, and laughably arrogant in the worst case.
           | 
           | > The reason inheritance causes so many issues in languages
           | like Java is because they are statically typed and also use
           | classes as types[1]. Classes must be somewhere in the
           | inheritance tree, hence you are forced into some place of
           | that tree. To make things worse, Java has many keywords that
           | restrict what inheritor of a class can do (private, final,
           | etc).
           | 
           | Fear not, Python is dynamically typed and inheritance is a
           | mess there as well.
           | 
           | > If someone expects you to implement Foo, you can (almost
           | always) just implement its relevant methods without
           | explicitly extending the class.
           | 
           | This is just structural subtyping (see Go's interfaces for a
           | statically typed example of structural subtyping) also known
           | as "duck typing". It seems like you're positing that the
           | problems with inheritance derive from _nominal subtyping_
           | (e.g., Java 's `implements` keyword), but these things are
           | orthogonal. Python has duck typing ("structural subtyping")
           | and its inheritance is no less painful than Java's.
           | Similarly, Rust has nominal subtyping (a type must explicitly
           | implement a trait) and it has none of the inheritance-related
           | problems that Python and Java have.
        
             | foobarian wrote:
             | I feel like OOP always had the nerd catnip problem. Since
             | the very beginning the various programming tutorials would
             | have the contrived examples of animals and canines and
             | dogs, or geometric shapes and triangles etc. which just
             | managed to ring a particular very satisfying bell in
             | people's heads. It was just such a neat concept with those
             | examples that just made sense. How it turned out in
             | practice is a different story but I feel this had a lot to
             | do with the enthusiastic uptake.
        
               | colllectorof wrote:
               | 1983 Smlltalk-80: The Language and Its Implementation by
               | Adele Goldberg and David Robson had pretty good example
               | with none of this animal/mammal/dog crap. Not sure when
               | the trend for giving awful examples like this really
               | started, but I don't think it was "from the very
               | beginning".
        
           | philwelch wrote:
           | To me there are about three tiers of this basic insight:
           | 
           | 1. Inheritance causes all kinds of issues so you shouldn't
           | use it.
           | 
           | 2. Actually, inheritance is fine as long as you do it right
           | (e.g. Liskov)
           | 
           | 3. Actually, getting part 2 right is difficult, and the heavy
           | risks of getting it wrong aren't worth the minor benefits of
           | inheritance.
        
           | nine_k wrote:
           | Polymorphism is good. You describe polymorphism.
           | 
           | Inheritance is bad. Inheritance is patching a class and
           | overriding some of its methods, while leaving others intact.
           | This brings all kinds of unexpected interplay between methods
           | of different levels of overriding. A typical example is
           | http://www.cse.psu.edu/~deh25/cmpsc473/jokes00/joke01.html
           | 
           | Ideally all "concrete classes" with method implementations
           | should be final, and the polymorphism should be achieved via
           | interfaces / typeclasses / traits, or purely abstract classes
           | where these are not available. Reuse of implementation should
           | be achieved via composition; there are several ergonomic ways
           | to express it.
        
             | piotrkaminski wrote:
             | > Simulator supervisors report that pilots from that point
             | onward have strictly avoided kangaroos, just as they were
             | meant to.
             | 
             | Won't fix; working as intended.
        
             | heavyset_go wrote:
             | > _Polymorphism is good. You describe polymorphism._
             | 
             | It also seems like they're describing the nominal typing of
             | Java versus a structural approach.
        
           | bccdee wrote:
           | You're describing interface-based polymorphism, which is what
           | go and rust use. In go, I can have a struct with methods that
           | implements a particular interface by implementing all the
           | methods described in that interface, but I can't _inhereit_
           | from another struct. The person you 're replying to called
           | this out as a better system too.
        
         | lowbloodsugar wrote:
         | Like anything, inheritance can be used poorly, just as anyone
         | can right poorly encapsulated code in Rust or Go. You might be
         | able to convince me that inheritance is too dangerous for
         | idiots, but then so is a computer, and we'd be debating where
         | to draw the line of how smart/experienced you have to be to use
         | it safely.
         | 
         | This article from Noel, and the ones from Mike he links to, get
         | under the hood and into "what is the compiler doing" and "what
         | is the CPU doing". Down here, we're looking at how to use the
         | features of whatever language we're using to get the results we
         | want, rather than "how should i program oop gud".
        
         | simiones wrote:
         | Go actually does implement inheritance, albeit in a roundabout
         | sort of way: a struct can have one or more base members, and
         | any method defined on the base members is accessible from the
         | new struct implicitly, so they also implicitly implement any
         | interface that was implemented by their base members.
         | 
         | Here's an example (in the playground, because it gets a bit
         | long): https://play.golang.org/p/TblQypAbIL2
        
           | throwaway894345 wrote:
           | That's not inheritance, it's just syntax sugar that lets a
           | struct delegate method calls to one of its members. In other
           | words:                   struct Foo {}              func (f
           | *Foo) baz() { println("Foo.baz()") }         func (f *Foo)
           | qux() { f.baz() }
           | 
           | Given the above, `struct Bar { Foo }` is the same as:
           | struct Bar { Foo Foo } // field called `Foo` of type `Foo`
           | func (b Bar) baz() { b.Foo.baz() }         func (b Bar) qux()
           | { b.Foo.qux() }
           | 
           | Since it's just syntax sugar and not inheritance, we can't
           | put a `Bar` in a list of `Foo`s nor can we pass a `Bar` into
           | a function that expects a `Foo`. It also means that if Bar
           | overrides its `baz()` method like so:                   func
           | (b Bar) baz() { println("Bar.baz()") }
           | 
           | that calling `Bar.qux()` will still print "Foo.baz" and not
           | "Bar.baz" (most languages with inheritance will print
           | "Bar.baz", which is to say methods are virtual by default).
        
             | tsimionescu wrote:
             | Using interfaces, you easily get polymorphism as well:
             | type FooI interface{ baz(); qux(); }       foos :=
             | []FooI{&Foo{}, &Bar{}}
             | 
             | Regarding overriding, you're right that it doesn't work out
             | of the box. However, you can make "extensible classes" with
             | just a little boilerplate:                 type Foo struct
             | {FooI this}       func (f *Foo) baz() {
             | println("Foo.baz()") }       func (f *Foo) qux() {
             | f.this.baz() }       func NewFoo() Foo { f := Foo{}; f.this
             | = &f; return f }
             | 
             | Now, to extend Foo:                 type Bar struct {Foo}
             | func (b *Bar) baz() { println("Bar.baz()") } //the override
             | func NewBar() Bar { b := Bar{}; b.Foo.this = &b; return b }
             | //this would work even if we didn't override baz
             | func main() {         foo := NewFoo()         bar :=
             | NewBar()         foos := []FooI{&foo, &bar}         for _,f
             | := range foos {           f.qux()         } //prints
             | Foo.baz(), then Bar.baz()       }
             | 
             | Since best practice even in C++ or C# or Java is to only
             | allow inheritance for classes that are designed with it in
             | mind, and since Go anyway has lots of other boilerplate,
             | this shouldn't be unbearable if required.
             | 
             | Playground link for anyone curious:
             | https://play.golang.org/p/SKGhANuBGgB
        
               | throwaway894345 wrote:
               | yeah, you absolutely can emulate this stuff to a large
               | degree. My point wasn't that it's impossible, but rather
               | you have to build it from orthogonal primitives. And even
               | then I don't think you can get the same degree of
               | trampolining that you can get with inheritance (for
               | example, we can get Bar.qux() to call Foo.baz() easily
               | enough via interfaces, but then IIRC it's trickier to get
               | Foo.baz() to call Bar.asdf()--that said I'm too busy to
               | think it through properly).
        
         | jayd16 wrote:
         | It's the dose that makes the poison. Of course we want nice
         | typed collection libraries and interfaces. The kids go
         | overboard though.
        
         | lkrubner wrote:
         | I've previously suggested that initiation is the "root of all
         | evil." See my essay:
         | 
         | Object Oriented Programming Is An Expensive Disaster Which Must
         | End:
         | 
         | http://www.smashcompany.com/technology/object-oriented-progr...
        
           | Arch-TK wrote:
           | Funny to see the author of one of my favourite blog posts on
           | the internet get downvoted.
        
         | vlunkr wrote:
         | I think that it can work. IMO ActiveRecord is a perfect use of
         | inheritance. You get tons of useful functionality out of the
         | box, you don't have to worry about what that code looks like,
         | and it's easy to extend or modify it. But often when I see co-
         | workers come up with their own hierarchies, it saves maybe a
         | couple of lines of code and makes it 5x more difficult to read,
         | since you're jumping between parent and child classes and
         | trying to keep track of the order of execution.
        
         | duped wrote:
         | I don't agree with this, and I'm personally an anti-OOP
         | militant.
         | 
         | Inheritance isn't the root of all evil, dynamic dispatch is.
         | It's a remarkably powerful implementation detail but one with
         | enormous cost, regardless of whether you're using an AoT/JIT
         | compiled or interpreted language.
        
           | bccdee wrote:
           | What enormous cost -- slightly slower function calls? That's
           | a pretty minor cost, and dynamic dispatch can be extremely
           | useful sometimes.
        
             | duped wrote:
             | Enormously slower function calls and zero inlining without
             | advanced dynamic compilation, which has a number of
             | pitfalls.
        
               | kaba0 wrote:
               | As opposed to passing function pointers everywhere,
               | callback hell and friends? Or which language does it well
               | in your opinion? The fact is, dynamic dispatch is needed,
               | because not everything can be known at compile time. And
               | as in a recent thread a HNer rightly noted (could not
               | find it where I read it), the actually expensive thing in
               | programming is flexibility.
        
               | bccdee wrote:
               | You must have absurd standards if you find dynamic
               | dispatch to be unacceptably slow. Also, yeah, not every
               | function call needs to be inlined. One level of
               | indirection on top of jumping into a new function isn't
               | really much overhead at all, unless you're doing it for
               | literally every function call.
        
               | throwaway894345 wrote:
               | Devirtualization optimizations can turn semantically
               | "dynamic" dispatches into static dispatches, but
               | sometimes you really just need a dynamic dispatch. Note
               | that a dynamic dispatch doesn't _have_ to be anything
               | more than a branch. Further, sometimes devirtualizing
               | everything leads to enormous binaries and compile times.
               | Runtime performance isn 't everything, and it's typically
               | better to opt-into devirtualization rather than to opt-
               | out of it.
        
           | throwaway894345 wrote:
           | Disagree. Go and Rust both have dynamic dispatch and neither
           | have the problems that inheritance has. Even in C which lacks
           | dynamic dispatch, people will either try to build it at the
           | expense of type safety or they will try to manage an
           | impossibly complex implicit state machine (I've seen this in
           | a lot of critical real time systems).
        
             | kaba0 wrote:
             | I would wager that in most ordinary C programs it is worth.
             | You can't even reason about code anymore with all the
             | callbacks.
        
       | cryptica wrote:
       | Can someone please point me to an example of a well-designed
       | (modular, maintainable) FP project (e.g. on GitHub)?
       | 
       | I've only had negative experiences so far and I can't imagine how
       | to use FP in a modular way (high cohesion, loose coupling) so I'd
       | like some examples to look at. A simple game would be nice.
        
       | HugoDaniel wrote:
       | One of the cool things in C is the space it leaves for the
       | compiler to perform optimisations.
       | 
       | Beyond what is observable, C compilers have almost free reins to
       | do whatever they want, so long as the observable things are kept
       | the same (output/memory address values, etc...).
       | 
       | Data oriented design was a smart-kid anti-pattern thing back
       | then, but I wonder if compilers have evolved enough so that it is
       | useless nowadays? It is by all measures a useless optimization,
       | since it can be placed hidden from the observable object
       | properties. (i.e. who cares if it is an array of structs or a
       | struct of arrays, so long as the vec3 is still a { x, y, z }?)
        
         | cjfd wrote:
         | No, compilers cannot do that. Who says that it is not the case
         | that in one cpp file the data access pattern is completely
         | different than in another cpp file? Hence, it may be that for
         | one ccp file the most efficient way would be to have an array
         | of structs and for another cpp file the most efficient way
         | would be a struct of arrays. The compiler cannot possibly know
         | this so it has to follow the data layout that the programmer
         | has specified.
        
         | ratww wrote:
         | It's not that simple. Like I said in another comment,
         | organising the data is only half the battle. This optimisation
         | also depends on your code accessing the data in an optimal way.
         | 
         | If the code itself is not organised for DOD, then the "optimal"
         | organisation is what we currently have. A per-entity _Update_
         | method that 's accessing different "kinds of data" will perform
         | worse with DOD-organised data.
         | 
         | This is why we have an architectural pattern that automates all
         | that, called ECS.
        
         | hsn915 wrote:
         | One of the core principles (truths?) behind data oriented
         | design is that compilers can never and will never be able to
         | compensate for unoptimized data layouts.
        
           | HugoDaniel wrote:
           | but they do, from the simplest automatic structure padding to
           | the more complicated flow analysis and packing/vectorization
        
             | DixieDev wrote:
             | The compiler isn't even smart enough to switch for-loops
             | that iterating over a grid when they would greatly improve
             | cache usage. Swapping the lines `for (x=0; x<width; x++)`
             | and `for (y=0; y<height; y++)` can give insane speedups on
             | typical modern hardware.
        
               | orwin wrote:
               | Are you sure about that? It must depend on the compiler.
        
               | astrange wrote:
               | They do have that optimization in some cases; it helps on
               | SPECint.
               | 
               | It often requires UB to optimize well, which many people
               | aren't into letting it do.
        
               | jcelerier wrote:
               | Both GCC and clang are able to do some level of loop
               | interchange optimization
        
             | megameter wrote:
             | Last I checked, field declaration order still mattered to
             | structure size and cache usage because the defacto packing
             | and padding rules preserve the order. I admit that I
             | haven't done any C optimization lately so I am curious. It
             | is possible to make this optimization, but it may disrupt
             | codebases that take shortcuts based on an assumed order.
             | And it would be especially difficult to do the analysis of
             | what should take precedence in the cache.
             | 
             | Could you link an example of compilers accommodating these
             | optimizations?
        
               | HugoDaniel wrote:
               | The clang documentation on vectorisation has a few
               | examples
               | 
               | https://llvm.org/docs/Vectorizers.html#slp-vectorizer
               | 
               | Cache precedence and cache line optimisations are black
               | magic, either you know specifically the cpu that you are
               | targeting, or rely on hopium techniques like cache
               | oblivious algorithms that try to reap some benefits.
               | 
               | The baseline is to measure, always, before and after
               | optimisation(s). These "Data oriented design" approaches
               | are very hard to measure and change rapidly because they
               | have a profound impact on a codebase, rarely ever change
               | "just one thing" and they err to the less intuitive and
               | less readable side.
        
             | cnity wrote:
             | How could a compiler possibly optimize for cache hits in an
             | array of structures? The only way it could do so is by
             | disobeying the programmers intention with the described
             | memory layout.
        
               | HugoDaniel wrote:
               | How can you? Do you know of a CPU that has specific
               | instructions to handle cache allocations?
        
               | ratww wrote:
               | If you have an array of small structures, all you have to
               | do is access all of them in one go with a _for()_ loop
               | and you 're already taking advantage of the cache. This
               | is enforced in ECS frameworks, btw: it's how a "System"
               | is implemented.
               | 
               | However, if your code has random access, there's no point
               | in using arrays of structures. The compiler would have to
               | modify the order of execution of instructions inside your
               | method to take advantage of how the data is laid out.
        
               | HugoDaniel wrote:
               | Sure there are great benefits in sequential access, some
               | of these can even be calculated to some extent.
               | 
               | However your reply does not answer the question.
               | 
               | Do you know of any CPU that has specific instructions to
               | handle cache? How can you be sure that you are gaming
               | cache lines when even the mnemonics are mostly
               | virtualised through all the pipeline and jump/memory
               | pattern predictions?
        
               | ratww wrote:
               | My reply is answering the first question, _How can you?_.
               | Not the second.
        
               | cnity wrote:
               | You simply take advantage of the fact that an entire
               | cache line is fetched when reading from memory, and keep
               | data that is frequently used together close to each other
               | in memory.
        
           | MaxBarraclough wrote:
           | This is true today, but I don't see that it must always be
           | so. I'm not a compiler researcher, but isn't there a
           | realistic chance that good things could happen if this became
           | a major research focus?
           | 
           | Somewhat related: I believe Jonathan Blow's currently
           | unreleased _Jai_ programming language is meant to do some
           | interesting new things in this area, enabling (or rather,
           | greatly simplifying) automatic transformations relating to
           | memory layouts.
        
             | username90 wrote:
             | > But isn't there a realistic chance that good things could
             | happen if this became a major research focus?
             | 
             | It has already been a major research focus the past 50
             | years or so. We have made great strides, yes, but we are
             | still very far from where it needs to be to compete with
             | lower level languages.
        
             | garethrowlands wrote:
             | It's not going to happen for C.
        
             | gnuvince wrote:
             | The compiler can automate some things (take a look at Zig's
             | MultiArrayList for example), but ultimately the programmer
             | must understand the data in their application, how it needs
             | to be transformed, and how to lay it out to be processed
             | efficiently by the hardware.
             | 
             | The compiler is a tool, you can set the field to help it do
             | its job, but it's no magic wand, it cannot think and
             | understand your application: only you can do that.
        
               | MaxBarraclough wrote:
               | > take a look at Zig's MultiArrayList for example
               | 
               | Thanks, I'd not seen that before, very neat. [0] I
               | thought it was just going to be an option to be row-major
               | or column-major, but no: _Instead of storing a single
               | list of items, MultiArrayList stores separate lists for
               | each field of the struct._
               | 
               | Could this be done in C++, perhaps with template
               | metaprogramming?
               | 
               | > The compiler is a tool, you can set the field to help
               | it do its job, but it's no magic wand
               | 
               | A paraphrasing of a familiar Mike Acton quote. Fittingly
               | it was mentioned in a blog post that mentions the Jai
               | language. [1][2] I find it frustratingly insubstantial.
               | Given we all presumably accept Rice's theorem and how it
               | applies to compiler optimization, it's a rather empty
               | quip, especially considering we're discussing future
               | possibilities.
               | 
               | Today's compilers are capable of some impressive
               | optimizations. It doesn't do to just dismiss the idea
               | that tomorrow's compilers might be able to do
               | significantly more with memory layouts.
               | 
               | Consider if, decades ago, a sceptic of optimizing
               | compilers had said: _Compilers are useful tools, but they
               | are not magic wands, and cannot achieve highly optimized
               | instruction-selection and register-allocation. It cannot
               | think and understand your application: only you can do
               | that._ The baseless suggestion that efficient register-
               | allocation is impossible in the absence of a strong AI,
               | would seem laughable today.
               | 
               | > it cannot think and understand your application: only
               | you can do that.
               | 
               | Today's compilers are not strong AIs, sure enough, but
               | that doesn't speak to the point here.
               | 
               | Optimizers and static-analysis tools _are_ capable of
               | reasoning about program behaviour. Again, you haven 't
               | justified dismissing the suggestion that future
               | optimizing compilers might be much more sophisticated at
               | this kind of transformation. Perhaps I'm an optimist for
               | arguing for the _sufficiently smart compiler_ , but it
               | doesn't strike me as beyond the realm of possibility.
               | 
               | Perhaps the closer answer is to adjust (or indeed
               | replace) our languages to be more amenable to memory
               | layout transformations than C/C++. This would presumably
               | be comparatively easy to implement.
               | 
               | [0] https://ziglang.org/documentation/master/std/#std;Mul
               | tiArray...
               | 
               | [1] https://blog.royalsloth.eu/posts/the-compiler-will-
               | optimize-...
               | 
               | [2] https://news.ycombinator.com/item?id=27010965
        
         | TheCoelacanth wrote:
         | What optimizations would hypothetically be allowed and what
         | optimizations the compiler actually has enough information to
         | be allowed to perform are very different.
         | 
         | A compiler is hypothetically allowed to convert array-of-struct
         | into struct-of-array, but to actually be allowed to do that it
         | would need to understand every single use of pointers in the
         | entire program. That is extremely challenging, if not
         | impossible.
        
       | orangepanda wrote:
       | Bit of a nitpick - what's with the "[headline] or [jk, here's the
       | real headline]" pattern? Pick one headline and use it.
        
         | mssundaram wrote:
         | I think it's user submitted vs mods editing the title
        
           | mssundaram wrote:
           | I see I misunderstood the question
        
         | n4bz0r wrote:
         | You have an article that falls under some broad category and an
         | issue that the article aims to address. There are not too many
         | ways to put these together.
         | 
         | One of them is to use an established, recognisable pattern that
         | makes an article look like it's from a newspaper or a book. I'm
         | no expert in this area, but my suspicion is that recognisable
         | title patterns bring higher viewership (or at least writers
         | think so).
         | 
         | Another thing with recognisable patterns is that they are being
         | more firmly imprinted in the back of your head the more you see
         | them. Perhaps it's not the views the author have pursued, but a
         | mere usage of whatever seemingly suitable pattern popped out of
         | his head first.
         | 
         | My vomiting reflex kicks in when I see repeating title patterns
         | too frequently, too. They make for an impression that the
         | articles are shallow. It's up to you to use this as an
         | indicator. I'm, for instance, rather fine with occasional
         | false-positives.
        
         | monocasa wrote:
         | It's a meme from older media for instance "Dr. Strangelove or:
         | How I Learned to Stop Worrying and Love the Bomb"
        
           | 9dev wrote:
           | Do you know where _that_ tradition in older media originated
           | from? I always wondered why books would have two titles.
        
             | bloak wrote:
             | There's some good information about that here:
             | 
             | https://en.wikipedia.org/wiki/Subtitle_(titling)
        
       | Bancakes wrote:
       | I still firmly believe the compiler should transpose your code
       | from array-of-structs to struct-of-arrays, and optimise for
       | temporal and spacial cache utilisation. After all, it is _the_
       | tool that knows every CPU arch on the planet and can run millions
       | of code transformations per second.
       | 
       | This could be achieved with C/C++ compiler directives, then pass
       | some PGO.
        
         | ratww wrote:
         | Transposing the data structures is trivial, but transposing the
         | code that access them might be impossible: inside your methods
         | you're still doing random access. You'd have to not only break
         | your _" Update"_ method into many, but also call those broken
         | parts separately.
        
         | astrange wrote:
         | Rearranging memory accesses is very difficult due to usually
         | not having enough info to do the alias analysis. Also, C/C++
         | compilers really don't know that much about their target
         | architectures, especially not their memory performance - they
         | barely do scheduling anymore since it's better to let the CPU
         | reorder it.
        
       | mamcx wrote:
       | What I wish to know is how apply this in a normal CRUD-like
       | scenario against a db.
       | 
       | It truly will help for, like, a shopping cart app?
        
       | moth-fuzz wrote:
       | Honestly I find ECS dogmatic and difficult. Data oriented design
       | as a general practice is a good thing, but there's layers to all
       | things. OOP is a very broad category of practices and designs,
       | and ECS usually refers to one specific architecture.
       | 
       | The specific architecture in question tends to be full of soft
       | dependencies - most ECSes don't allow you to simply store a piece
       | of data without opting in to all systems matching the data type
       | executing arbitrary code on it. So much for separation of data
       | and behaviour. No, now they're even More dependent than they are
       | in the usual OOP sense and you might not even know it.
       | 
       | Furthermore, usually when you want to think about in-game
       | entities, you want to look at a single class. Now all entities'
       | data is split into numerous components and all entities'
       | behaviour is split into numerous systems and you don't know frame
       | by frame how they're gonna interact, provided you even know about
       | all systems in the first place. It's total spaghetti code.
       | 
       | I'm much more inclined lately towards a shallow actor system . If
       | I want to know how player's behaviour functions, I need only look
       | at player.cpp and nothing else. Then, to reap the benefits of
       | data oriented design, certain objects can use a certain
       | allocation scheme that makes sense for the object in question. In
       | the general sense, any Component<T> can just have a vector<T> or
       | an unordered_map<T> of all components and the memory access is
       | abstracted away without it being detrimental. That's C++'s whole
       | deal actually, zero cost abstractions.
       | 
       | I wouldn't call an entire paradigm shift in which one rewrites
       | everything from memory allocation all the way up to 'use WASD to
       | move' zero-cost in any sense.
       | 
       | In C++ it is trivial to overload new, or derive from some class
       | which does, or to write a custom allocator, and frankly there is
       | zero need for the same person who's writing 'use WASD to move' to
       | know about memory management.
        
         | setr wrote:
         | The way I view it is that you look at components like a
         | database -- a bunch of tables that don't really tell you much
         | about the business logic; the main thing they do is offer data
         | coherence.
         | 
         | And like a webserver, the business logic has been entirely
         | moved out of the data storage -- you construct an "object" out
         | of the raw parts from the database, and you operate on that.
         | The main thing is that you can construct multiple objects from
         | the same raw dataset -- different views (as in MVC).
         | 
         | I think however it's a mistake when you assume most game's
         | design -- where largely there are a very small amount of
         | entities, and a small amount of behaviors, and really the game
         | design is about careful placement of these fairly rudimentary
         | entities on the map. In that scenario, the flexibility of ECS
         | does you no good -- the game design is itself inflexible, so
         | making your logic flexible is largely a premature optimization.
         | If there's only one reasonable "View" of the entity, being able
         | to construct an infinite set of alternative views is pointless.
         | 
         | ECS appeals to me more when you start talking about simulation-
         | style games, where the game is far less hard-coded. Dwarf
         | Fortress is ridiculously flexible in its game design (at
         | runtime), and ECS would be a natural fit for that (entities in
         | DF are literally defined by tags, and groups of tags, and those
         | tags get modified at runtime[0]). It's not spaghetti code then
         | -- it's really the only reasonable way to approach the problem.
         | 
         | Defining each entity uniquely makes a lot of sense when your
         | entities are largely unique (perhaps with a common base, e.g.
         | for physics). ECS makes more sense when your entities share of
         | lot of logic, but random subsets of it, and especially so when
         | the game itself treats that random subset as dynamic.
         | 
         | [0] http://www.dfwk.ru/Creature_standard_1.txt
        
           | moth-fuzz wrote:
           | Dwarf Fortress is one of my favorite games if not my favorite
           | so props for citing its internal representation. I definitely
           | think ECS makes sense in many contexts, and I think it's at
           | its most powerful in tandem with a more encapsulated actor
           | system. Example -                 struct Player: public Actor
           | {         Component<Sprite> sprite;
           | Component<Transform> transform;         void update()
           | override {...}       };
           | 
           | Where Component<T> is a handle to some backing storage
           | indexed by an Actor's ID. This way, you can go the
           | traditional route of having Player update itself in its own
           | update() method as well as being able to
           | Component<Sprite>::iter() along with other components for the
           | render loop ECS-style.
           | 
           | My point now and my point then was going all-in on ECS as the
           | basis for your entire architecture rather than taking a more
           | principled approach drawing the strengths of OOP and DOD is
           | dogmatic and difficult.
           | 
           | It's interesting that you mention web services - I have a lot
           | of respect for databases and I enjoy the process of using
           | them, however, I wouldn't ever program a web app _in_ SQL.
           | That 's what pure ECS feels like to me. I agree that having
           | objects manipulate the state via upholding internal
           | invariants is the way to go. Whether you call them Actors, or
           | Systems, or Controllers, it's kinda one and the same. That's
           | why I love DOD as a base architectural layer to be abstracted
           | upon, but dislike ECS as a programming paradigm.
        
         | lamontcg wrote:
         | After studying ECS for all of a week, I was left wondering if
         | there wasn't a way to reintroduce strong typing to an ECS
         | system (without reintroducing all the problems of inheritance).
         | So you have a player_entity factory that ensures that a
         | player_entity only wrap entities that are actually players.
         | Then you can pass that around to strongly typed functions, but
         | keep the overall design reasonably inheritance-free so it was
         | more like rust/go's strong typing systems.
        
           | pjmlp wrote:
           | In the case of C++, you can do that via templates and static
           | dispatch, now with C++20 is even better as each entity can be
           | modelled as a concept.
           | 
           | something like,                   template<typename T>
           | concept Player = requires (T p) {             p.jump();
           | };              class Factory {             public:
           | template<Player p>             void register_entity(p
           | player);         };
           | 
           | Just as seed for the overall idea.
        
           | moth-fuzz wrote:
           | Back when I was trying to design an ECS engine I had a class
           | Prefab<T...> which you would inherit from using CRTP to
           | define entities with stronger static typing. For example,
           | Player would be                 class Player: public
           | Prefab<PlayerController, Transform, Sprite> {};
           | 
           | or something like that. C++ is infinitely flexible in that
           | regard. I was never quite able to work out what would happen
           | if you were to add/remove components at runtime to such
           | Prefab classes. The semantics never quite made sense to me in
           | terms of static typing, because it's no longer static.
        
           | jms55 wrote:
           | I've thought about this in the past too, and come to the
           | conclusion it is too difficult. Part of what I don't like
           | about ECS is that it's too dynamic, you can't add static
           | typing like this.
           | 
           | Sure, you can say that a "Skeleton" entity has an HP
           | component, an AI component, etc. But there's no way of
           | enforcing static typing on this.
           | 
           | Say you have some function that takes an Entity, validates
           | it's a Skeleton, and then returns a SkeletonEntity which is
           | just a wrapper around Entity for static typing purposes.
           | Perhaps add some helper methods for fetching components,
           | allowing you to operate on an OOP-like API with very little
           | runtime cost. Seems like it works.
           | 
           | But there's no way to guarantee the skeleton STAYS as a
           | skeleton for the future. You might add another thing later on
           | that converts an entity with AI into FriendlyAI, and your
           | SkeletonEntity implicitly relied on the AI being a
           | SkeletonAI. Heck, you can't trust _any_ Entity. That handle
           | to an entity you have might not even point to an Entity
           | anymore, it could've been deleted.
           | 
           | ECS is very dynamic, which is great for designing open-ended
           | games where you don't/can't plan every interaction in
           | advance. It also has great performance, and is typically the
           | _only_ sane way to implement games in a language without
           | inheritance (I don't think Composition + Interfaces is very
           | scalable). But for heavily structured games that want tight
           | coupling between entities, it relies on you implicitly
           | keeping your promises about what an entity means. You can't
           | go and add new behavior that modifies existing entities later
           | on - The compiler won't warn you, and you may end up crashing
           | your program at runtime, unless you were very diligent about
           | adding checks in your code for the coupling you assumed
           | existed, and having good fallbacks for one those assumptions
           | are violated.
        
       | bob1029 wrote:
       | You can get some pretty unbelievable performance gains out of a
       | single writer and arrays of structs.
       | 
       | Bonus points if you figure out a way to have an array per type of
       | struct pre-allocated with more elements than you will ever need.
       | Even if you use a GC language you can almost eliminate
       | collections with this approach.
        
         | PicassoCTs wrote:
         | Even the array of structs is a non-ideal approach, as structs
         | are usually viewed as a static collection of data.
         | 
         | But if you look at the hot loop, it usually boils down to a
         | funnel - not unlike a furnace. Lots of highly spacious needing
         | raw materials are gathered and passed through, to be condensed
         | into relatively small output.
         | 
         | So the ideal structure is a sort of union-struct, that
         | compresses the results down each step of the algo, keeping it
         | all in cache, while keeping it slim..
        
           | moldavi wrote:
           | Would love to hear more about that kind of compressing, how
           | does that go?
        
       | urthor wrote:
       | People forget what the _intent_ of OOP originally was.
       | 
       | OOP was envisioned as a way to manage _software projects with
       | many contributors_ at a time when we didn 't have half the tools
       | for hiding context that we do now.
       | 
       | Micro-services and micro-kernels are far far far more prevalent
       | these days.
       | 
       | Garbage collection was also far less of a thing in that era, as
       | all programmers were squeezing every last iota out of the
       | hardware.
       | 
       | Hence rogue pointers were _far_ more of a risk.
       | 
       | Multi-core? Haha.
       | 
       | I know this is not particularly relevant to the original article,
       | but if you don't know the history and the _intent_ behind why
       | something exists, you are reasonably likely to misapply it.
       | 
       | Most of the mistakes of OOP are from a lack of understanding of
       | why things got invented in the first place.
        
         | beders wrote:
         | The original intent had nothing to do with "many contributors".
         | 
         | The main ideas: encapsulation, message passing and late binding
         | i.e. dynamic binding.
        
         | BoiledCabbage wrote:
         | Also, the article does a really poor job of describing any
         | drawbacks of Data Oriented Design. It's a real pet-peeve of
         | mine.
         | 
         | > Drawbacks of Data-Oriented Design Data-oriented design is not
         | the silver bullet to all the problems in game development.
         | 
         | Ok, they don't view it as a silver bullet. This seems promising
         | for an evenhanded discussion. I'm curious what the author
         | thinks the drawbacks are.
         | 
         | > The main problem with data-oriented design is that it's
         | different from what most programmers are used to or learned in
         | school.
         | 
         | So the first drawback is that nobody knows your silver-bullet?
         | That's a cop out.
         | 
         | > Also, because it's a different approach, it can be
         | challenging to interface with existing code, written in a more
         | OOP or procedural way.
         | 
         | And the second drawback is that code was written without using
         | your silver-bullet? Seriously?
         | 
         | If the only two things you believe are drawbacks about your
         | tech are that not enough people know it, and not enough people
         | are using it then it's not an even handed discussion of your
         | tech.
         | 
         | Discuss the actual trade-offs you've learned from using it. Not
         | nonsense like nobody knows how wonderful it is, nor is using
         | it.
         | 
         | And that's coming from someone who agrees that OOP has huge
         | flaws and with the most common applications of inheritance
         | creates many flawed program architectures.
        
       | ncmncm wrote:
       | The great mistake is to believe there is something useful called
       | "Object-oriented", or "Data-oriented", or "Functional", or what-
       | have-you-oriented programming.
       | 
       | Carpenters don't learn "hammer-oriented" building; or "saw-
       | oriented", "screwdriver-oriented", "chisel-oriented", or "glue-
       | oriented". They learn to use hammers, saws, screwdrivers,
       | chisels, and glue, and use them all at different times, sometimes
       | one more than others. Machinists don't learn "lathe-oriented",
       | "drill-oriented", "mold-oriented", or "welding-oriented"
       | fabrication. They do all or any of them strictly according to
       | what they are making, and what they are making it out of.
       | 
       | It is as utterly stupid to design a whole language around one of
       | them as it would be for a carpenter to try to run a
       | screwdrivering business. A language is useful exactly to the
       | degree that it enables building whatever you might be called upon
       | to build.
       | 
       | That is not to say any generally useful language is equally good
       | for any purpose. Wood is better for some products, metal for
       | others. A metal violin would be weird, a wooden gun action would
       | be stupid. But violins often have metal bits and guns often have
       | wood stocks.
        
         | winstonewert wrote:
         | I have to disagree with the sentiment you express here.
         | 
         | Your metaphor is bit confused. You are comparing individuals
         | tools to methodologies. For example, object oriented
         | programming is a methodology which involves the use of a
         | variety of the tools like inheritance, composition, interfaces,
         | dependency injection, etc. So, object-oriented programming does
         | not correspond to always using a hammer in every situation. It
         | rather corresponds to particular theory about which tools to
         | use in which situations.
         | 
         | There have been a variety of methodologies of framing
         | buildings. There was traditional framing, then balloon framing,
         | and now platform framing. These different approach to framing
         | are what would actually corresponding to things like procedural
         | programming, objected programming, and functional programming,
         | not the individual tools.
         | 
         | It seems to me that it is pretty clear that there is something
         | usefully termed data-oriented programming or functional
         | programming. Neither is appropriate to all scenarios, but that
         | doesn't prevent them from being useful concepts.
         | 
         | Crucially, though, your thrust seems to be that we should use
         | the right approach for the task at hand. That's true to a
         | degree. But what remains is that some paradigms are either bad
         | ideas or frequently get applied in domains they aren't suited
         | to (I'm looking at you OO!).
         | 
         | The consequence is that I don't see it as particular helpful to
         | say "use the right tool for the job." Yes, it is true, you
         | should use the right tool for the job. But I think its also
         | true that some paradigms are better then others, just as some
         | framing techniques are improvements on anothers.
        
           | ncmncm wrote:
           | If you start building a skyscraper by nailing up 2x4s, you
           | won't get very far. But that says nothing about the merits of
           | nailing up 2x4s.
           | 
           | If you start a project thinking, "I'm going to make an
           | object-oriented program", or "I'm going to make a data-
           | oriented program", you are just working with one hand tied
           | behind your back. A mature programmer mixes elements freely
           | with no thought to "orientation".
           | 
           | If you demand simple advice: Don't Be Stupid. It's not
           | helpful, but neither is TFA. At least mine is correct.
        
             | winstonewert wrote:
             | > A mature programmer mixes elements freely with no thought
             | to "orientation".
             | 
             | Seriously, you advocate freely mixing radically different
             | architectural approaches? Well... I don't want to share a
             | codebase with you.
        
               | ncmncm wrote:
               | I advocate freely using language features anywhere they
               | are useful.
               | 
               | Architecture has nothing to do with seminar-peddlers'
               | "paradigms". Architecture is about organization. For each
               | problem, some architecture is uniquely suited to
               | addressing it. If you constrain yourself to this or that
               | paradigm, you commit at the outset to failing to arrive
               | at the optimal organization for the actual problem.
        
         | rdw wrote:
         | There are different methods of framing a house and roof and
         | builders do tend to specialize in just one. Balloon framing is
         | different than platform framing which is different than mortise
         | and tenon framing. To learn a new system, a carpenter would
         | have to learn a whole new system of rules, measurements,
         | suppliers, attachment methods, and seasonal tolerances. Here's
         | an overview of some of these techniques:
         | https://www.hometips.com/diy-how-to/house-framing.html
         | 
         | It's actually pretty analogous to programming methods.
        
           | ncmncm wrote:
           | Different methods of framing correspond to different
           | materials available. Long 2x4s are too expensive now for
           | balloon framing to be cost-effective, just as posts and
           | beams, and lath and plaster, became too expensive (for
           | different reasons), earlier. None is abstractly better than
           | the other, even for identical end products.
           | 
           | In some places bricks are, or once were, cheaper than lumber.
           | A carpenter who can't build a house with brick walls, or
           | can't add a platform-framed room to one, is no carpenter at
           | all.
        
         | throwaway894345 wrote:
         | This is just a rephrasing of a boilerplate response to any
         | criticism at all: "there is a time/place for OOP!" followed by
         | precisely zero articulation about when one should use OOP over
         | a rival paradigm. It is one of the weakest arguments I can
         | conceive of (perhaps following "but that's not _true_ OOP "
         | with no assertion about what characterizes OOP, or a definition
         | of OOP that is only esoteric).
         | 
         | Why do you feel the need to talk to programmers using a
         | construction metaphor rather than talking about programming
         | paradigms directly? If OOP has merit, shouldn't it be
         | relatively easy to articulate as _to programmers_? What is OOP
         | good at? When should I use it rather than a data-oriented
         | approach? Why talk in tenuous metaphor?
        
           | ncmncm wrote:
           | You _just read_ that the whole notion of object-oriented (or
           | anything-oriented) programming has no merit, and are now
           | asking me for examples where it has merit.
           | 
           | You are usually constrained to a particular language or,
           | sometimes, mix of languages. You have access to a set of
           | language features. Use them in any mix that solves the
           | problem. If runtime polymorphism would be useful, use it. If
           | arrays would be useful, use them. If recursion would be
           | useful, use it. If compile-time type matching would be
           | useful, use it.
        
             | throwaway894345 wrote:
             | Thank you, I always appreciate a thoughtful critique.
        
         | teakettle42 wrote:
         | > The great mistake is to believe there is something useful
         | called ... "Functional", or what-have-you-oriented programming.
         | 
         | It's a remarkably substantial mistake -- "the great mistake",
         | even -- to blithely discard a concept so fundamental to
         | computer science and programming language theory that we
         | literally could not progress in the field without it.
         | 
         | You may as well be an earth-bound carpenter denying the
         | existence of "gravity-oriented design".
        
           | ncmncm wrote:
           | Yet, we _did_ progress without it.
           | 
           | Learning a program organization style is helpful for
           | beginners, if the toy problems they get are more easily
           | solved using the style. But you progress, ultimately, by
           | transcending it. A mature programmer mixes elements freely
           | without labels.
           | 
           | Crawling-oriented locomotion is fine for babies. Adults walk,
           | run, swim, drive, pilot, ride, sometimes even crawl. Learning
           | to run does not make you worse at crawling, or make you run
           | when you should crawl.
        
             | teakettle42 wrote:
             | > Yet, we did progress without it.
             | 
             | No, we didn't, and claiming so merely demonstrates your
             | profound ignorance regarding the most basic fundamentals of
             | our field.
        
               | ncmncm wrote:
               | We must be working in wholly different fields. I make
               | software that, you know, works.
        
         | TillE wrote:
         | The weird thing about this article's category of OOP criticism
         | is that it makes all sorts of assumptions about class design
         | and memory allocation which are absolutely not intrinsic to
         | OOP.
         | 
         | Like, there are plenty of good points to be made here without
         | discarding the entire idea of object-oriented design. It really
         | just needs a little tweak.
        
         | marviel wrote:
         | I don't have a substantive response except to thank you for
         | giving me a helpful analogy in future discussions on the topic.
        
       | davedx wrote:
       | I was expecting to see some example code, or some actual
       | performance metrics to show why data-oriented design is better.
       | 
       | I actually have written a game that was pure functional style
       | with a single giant state object for the game data and it worked
       | well for me. But I'd want to see some evidence for this approach
       | before changing the entire architecture of my game.
        
         | meheleventyone wrote:
         | This graph made the rounds on Twitter last week and I think
         | encapsulates the answer really well:
         | https://twitter.com/eric81766/status/1407393532562841607
         | 
         | Most games won't benefit. Most AAA games won't benefit for
         | their gameplay code and are otherwise very data-oriented
         | already.
        
           | davedx wrote:
           | ECS isn't the same thing as data-oriented programming is it?
           | I've worked on AAA games and this whole discussion is quite
           | confusing, lol.
        
             | meheleventyone wrote:
             | Nope! If you want a concrete example I'd recommend looking
             | at the Unity DOTS stuff which is their data-oriented stack
             | and does include an ECS as part of it.
        
       | pulse7 wrote:
       | Overuse/misuse of inheritance has triggered hatred of OOP among
       | many software developers...
        
         | brobdingnagians wrote:
         | Yeah, I've loved classes and OOP for a long time, but lately
         | I've been on a project that is going into more and more
         | contortions and complexity to make everything fit a theoretical
         | ideal. It's revived my interest in learning FP languages to
         | avoid the arcane complexity that some people make out of OOP.
        
           | lelag wrote:
           | I feel complexity creep is often inevitable regardless of the
           | paradigm or technology you use. Programmers have varying
           | levels of complexity they can handle and the systems
           | generally naturally grow to match the complexity that the
           | people that work on them can support.
           | 
           | Experienced programmers will manage to keep that complexity
           | creep under control for longer and smarter programmers will
           | manage to keep working on complex systems that lesser peers
           | would have no chance to understand.
           | 
           | But eventually, unless the software has a very clear
           | functional boundary which is often not the case for business
           | software, software will start to become increasingly complex
           | and dev velocity will slow down, quality will drop... I've
           | seen this happening at all skill levels regardless of the
           | languages and paradigms used.
        
             | qayxc wrote:
             | > I feel complexity creep is often inevitable regardless of
             | the paradigm or technology you use.
             | 
             | This. I always love toy examples that look so nice,
             | concise, and elegant. Until you add proper error-handling
             | and corner cases that is; then all of a sudden it doesn't
             | look half as concise, elegant, and beautiful anymore...
        
             | MillenialMan wrote:
             | The problem with inheritance-heavy OO is that complexity
             | creep rapidly makes the code completely unreadable and
             | incredibly hard to work with (and basically impossible to
             | refactor), rather than just making it more branchy and
             | complex.
        
         | throwaway894345 wrote:
         | I'll go out on a limb and posit that there are _virtually_ no
         | valid uses of (implementation) inheritance. Perhaps one valid
         | use is getting rid of delegation boilerplate (e.g., normally
         | you would compose one object inside another but you want the
         | outer object methods to delegate to the inner object methods
         | but you don't want to have to write N function definitions that
         | just call the same methods on the inner object so instead your
         | outer object inherits from your inner object). This problem is
         | better solved by something like Go's struct embedding since it
         | doesn't do anything more than this kind of automatic
         | delegation.
         | 
         | And if you get rid of inheritance, there is very little left to
         | distinguish OOP from procedural programming like one would do
         | in C or Go. And this is the semantic problem: no one really
         | agrees on what OOP is and proponents will rebut any criticism
         | with "that's not _true_ OOP". Any definitions of OOP that
         | aren't easily assailable are also indistinguishable from other
         | existing paradigms.
         | 
         | Downvoters: i'm very interested in your opinions about why I'm
         | wrong and specifically when you think inheritance is
         | appropriate. Everyone says "there's a time and a place!" but no
         | one articulates when/where beyond cat/dog/animal toy examples.
        
           | colllectorof wrote:
           | Alan Kay spent the last 40 years educating people on OOP and
           | system design. His talks and research papers are now widely
           | available on the internet. There are free, modern and easy-
           | to-use versions of Smalltalk. Anyone who still remains
           | ignorant about fundamental ideas behind classic OOP and the
           | paradigm's history is _willfully_ ignorant.
        
             | throwaway894345 wrote:
             | Lots of OOP proponents disagree strongly with Kay's
             | definition of OOP, and his definition certainly doesn't
             | reflect the way the most popular self-described OOP
             | languages are written today. Notably, Smalltalk has a
             | negligible share of the market, so why should anyone waste
             | time debating Kay/Smalltalk's notions of OOP when they are
             | at best niche?
             | 
             | Further, and more relevant to the thread at hand: it's not
             | clear to me that Kay's notion of OOP considered inheritance
             | to be a critical feature. To quote him:
             | 
             | > I felt somewhat the same way about inheritance as I did
             | about types, in that both needed to be a lot better than
             | they were in order to pay for the overheads and pitfalls of
             | using them.
        
               | rytor718 wrote:
               | I think I agree with you. However to the parents point: i
               | think the implication is we might be enlightened about
               | why Alan defines OOP the way he does when we
               | contextualize it with Smalltalk, the language in which he
               | used it. That's a fair point.
               | 
               | But again, you're right: most of us aren't familiar with
               | Smalltalk and so find the very idea of reading such
               | papers daunting at best. I think I'll finally try it
               | though ...it can't be that hard of a language to grasp
               | and it may well lead to some insights about _why_ OOP, as
               | defined by Mr. Kay, is defined as such.
        
               | throwaway894345 wrote:
               | > I think I agree with you. However to the parents point:
               | i think the implication is we might be enlightened about
               | why Alan defines OOP the way he does when we
               | contextualize it with Smalltalk, the language in which he
               | used it. That's a fair point.
               | 
               | I absolutely agree that understanding Kay and Smalltalk
               | can help one become a better programmer and give context
               | into the history of OOP. But it can't be interpreted as
               | anything other than a semantic deflection in the context
               | of a response to substantial criticism.
        
               | karmakaze wrote:
               | I've never heard this brought up before. What's the
               | distinctions? The thing I've noticed and found lacking in
               | modern OOP is that it tends to be class-based without
               | metaclasses or metaprogramming. Is there something else?
               | Static typing is also something that not in Smalltalk,
               | but that shouldn't change the network shape of objects.
        
               | throwaway894345 wrote:
               | Here are some of the definitions I've heard:
               | 
               | * OOP is about message passing (where message passing is
               | _NOT_ method invocations)
               | 
               | * OOP is about message passing (where message passing
               | _can be_ method invocations)
               | 
               | * OOP is about encapsulation (never mind that most/all
               | paradigms make extensive, idiomatic use of encapsulation
               | --some OOP proponents suggest encapsulation implies
               | constructors that do lots of work, take ver few
               | arguments, and make the class virtually untestable,
               | others argue that this is an "abuse" of OOP or "bad
               | programming")
               | 
               | * OOP is about inheritance
               | 
               | * OOP is a Kingdom-of-nouns programming style
               | (effectively Joe Armstrong's "You wanted a banana but
               | what you got was a gorilla holding the banana and the
               | entire jungle" observation)
               | 
               | For all of these definitions, I've heard many OOP
               | proponents argue that these things are not _true OOP_
               | (typically without rebuke from other OOP proponents in
               | the forum, bizarrely).
               | 
               | In my opinion, OOP must be defined by the things that
               | distinguish it from other paradigms. Considering
               | encapsulation and method calls are both fundamental to
               | other paradigms, these cannot be defining characteristics
               | of OOP. Additionally, any defining characteristic of OOP
               | must be shared by languages that are virtually
               | universally recognized as OOP, which means that message
               | passing in a non-method-call sense must be excluded. That
               | generally leaves inheritance, "extreme encapsulation"
               | (untestable constructors), and kingdom-of-nouns
               | programming styles.
               | 
               | I don't think the "class-based" thing is meaningful
               | because apart from inheritance there's not much to
               | distinguish a "class" from a struct in Go or Rust (in
               | both cases you can associate methods to the struct for
               | interface polymorphism) which are generally not
               | considered to be "OOP languages" (and Go certainly
               | doesn't have metaclasses or metaprogramming).
               | 
               | > Static typing is also something that not in Smalltalk,
               | but that shouldn't change the network shape of objects.
               | 
               | I agree that static typing is not a defining
               | characteristic of OOP, and I've never heard anyone argue
               | that it is.
        
               | pulse7 wrote:
               | * message passing in Smalltalk is implemented as method
               | invocation (the same is in Java, C#, C++, ...) *
               | encapsulation in Smalltalk: all fields are private/hidden
               | (but: all methods are public)
        
           | inopinatus wrote:
           | Well, since you ask, I downvoted you for restating the false
           | equivalence between OOP and inheritance, for the abjectly
           | incorrect (and also, wholly unconstructive) straw-man
           | statement that no-one agrees what OOP is, for the frankly
           | absurd and wilfully ignorant claim that no-one articulates
           | practical examples for the circumstances when inheritance
           | rather than composition might actually be worth considering,
           | and for bitching about downvotes, particularly that explicit
           | assumption that they're coming from people wedded to
           | inheritance, and not from people who dislike disingenuous
           | arguments and circular reasoning.
           | 
           | Read a fucking book, instead. David West's _Object Thinking_
           | , for example.
        
             | throwaway894345 wrote:
             | > Well, since you ask, I downvoted you for restating the
             | false equivalence between OOP and inheritance, for the
             | abjectly incorrect statement that no-one agrees what OOP
             | is, for the frankly absurd and wilfully ignorant claim that
             | no-one articulates practical examples for the circumstances
             | when inheritance rather than composition might actually be
             | worth considering, and for bitching about downvotes,
             | particularly the explicit assumption that they're coming
             | from people wedded to inheritance, and not from people who
             | dislike disingenuous arguments of any form. Read a fucking
             | book. David West's Object Thinking, for example.
             | 
             | Thank you for clarifying that your downvote was _definitely
             | not_ an emotional overreaction. :)
        
               | inopinatus wrote:
               | > Thank you for clarifying that your downvote was
               | definitely not an emotional overreaction. :)
               | 
               | Smileys don't turn snide into humour, so now you can add
               | "naked ad hominem" to the litany of downvote attractors.
        
               | throwaway894345 wrote:
               | I thought it was the italics. In which ever case, this
               | conversation has _definitely_ been fruitful and
               | enlightening. Thank you for your contributions.
               | 
               | .
               | 
               | .
               | 
               | .
               | 
               | .
               | 
               | .
               | 
               | :)
        
               | inopinatus wrote:
               | This line of vacuousness merely continues to highlight
               | how brittle and indefensible the original arguments were.
        
           | Ensorceled wrote:
           | In my case, I'm using Django on several projects. It uses
           | inheritance to implement the ORM, the views, filters, etc.
           | etc.
           | 
           | When you say "there are virtually no valid uses of
           | (implementation) inheritance" ... how do you expect the
           | thousands of us using Django to respond to that? A link to
           | the Django framework? To defend Django? What is the point?
           | 
           | You're right, maybe python's object model could have instead
           | been implemented like Go's struct but it wasn't.
        
             | throwaway894345 wrote:
             | I don't know enough about Django's API to speak
             | intelligently, but I have 15 years of Python experience and
             | (except when APIs require it) it's perfectly easy to write
             | Python code without using inheritance (and your code base
             | will be better because of it). If Django or whatever
             | requires using inheritance, you have my sympathy, and I'm
             | not arguing that you should go to great lengths to avoid it
             | --I'm arguing that from a language design perspective
             | inheritance is a mistake.
        
             | formerly_proven wrote:
             | IMHO there's quite a difference between what's technically
             | inheritance in an ORM (I am assuming you mean writing
             | "class MyModel(orm.model):", not in-database-inheritance)
             | and Java'esque tree hierarchies of classes.
             | 
             | The former is mostly about invoking a type operator /
             | metaclass [1] to construct a model class from your
             | declarative specification.
             | 
             | I don't know what the latter is about. I think deep
             | inheritance (where deep means like "more than 2") are
             | virtually always a mistake. Stuff like toolkits that go
             | Object>Widget>AbstractButton>PushButton might be an
             | exception but I'm not entirely sure, there's probably a
             | better way.
             | 
             | [1] I think metaclasses aren't type operators in the strict
             | sense, because they're not handed a finished type, but
             | rather the declaration of a type, and then create a type.
             | Maybe there's a word for that.
        
           | pulse7 wrote:
           | Some cases where inheritance may be useful: UI widget
           | libraries, graphical/drawing objects, (very similar) stream
           | implementations. All this can of course be done without
           | inheritance, but with inheritance it is much more elegant.
        
             | divs1210 wrote:
             | Disagree. See reagent[0].
             | 
             | [0] https://reagent-project.github.io/
        
             | throwaway894345 wrote:
             | Thank you for responding.
             | 
             | I probably agree with the widget library example, but even
             | here a widget library is just one way of implementing a GUI
             | toolkit and perhaps it's just the emergent result of an
             | OOP-based approach (and consequently there are some really
             | brutal tradeoffs in using a widget-based approach for a
             | toolkit). To that point, with respect to general drawing
             | libraries, I don't think that OOP/inheritance yields a more
             | elegant design than a reactive or intermediate mode
             | approach (perhaps these terms only apply to GUI, but I
             | imagine there are parallel terms for graphical APIs in
             | general).
             | 
             | Even though I concede narrowly on the widget GUI library
             | point, these libraries are so very rare that I don't think
             | it's a very compelling case for OOP proponents. I suspect
             | they'd like to argue that there are appropriate uses for
             | inheritance _in most applications_ and not just the odd GUI
             | library. Otherwise I don't think it even merits first-class
             | language support (why bother with an `extends` keyword if
             | it's only going to be useful in the rarest of libraries?).
        
       | shadowgovt wrote:
       | Something I really enjoy about golang was the design decision to
       | make structured data stay separate from function, but then
       | provide a simple mechanism for defining functions that worked in
       | the context of structured data. It feels like a good split
       | between the two desired uses for structured data... On the one
       | hand, it's easy to encapsulate the data for common use case, and
       | on the other hand I can generally trust that if I need to bit-
       | bash a data structure (i.e. use only a piece of it, or introspect
       | it, or serialize it), I can do that with a minimum of care as to
       | ny object-like metadata it might be carrying.
        
       | FpUser wrote:
       | I've been "shooting myself in the foot" for 40 years already to,
       | thank you.
       | 
       | OOP, FP, Data Oriented, insert your favorite here are just a
       | friggin' tools in the arsenal and all are fine when used
       | appropriately. One does not negate the other.
       | 
       | You just do not use the approach used when writing firmware for
       | low power microcontroller for writing Solidworks. And it is ok to
       | mix and match if type of software to be written benefits.
       | 
       | There are no silver bullets in this world and one must learn what
       | to use and when, Trying to convince people to stick to just one
       | is a great disservice and looks more like a religious propaganda:
       | if I do it this way the others should do the same disregarding.
       | 
       | Oh and btw OOP can be cache friendly as well. Nobody forces one
       | to organize internal data representation in any particular way.
        
         | gnuvince wrote:
         | > Oh and btw OOP can be cache friendly as well. Nobody forces
         | one to organize internal data representation in any particular
         | way.
         | 
         | This is part of the argument of the article, that although
         | nothing forces you to, there is a _culture_ that strongly
         | suggests a way of doing things.
        
           | FpUser wrote:
           | >"there is a culture that strongly suggests a way of doing
           | things"
           | 
           | Sorry but I would not take "strong suggestions" coming from
           | prophets with vested interests for granted. Or if said
           | culture comes from generic ignorance / lack of knowledge.
        
       | lmilcin wrote:
       | Sigh... This again.
       | 
       | I am going to repeat this for what seems like a ten thousandth
       | time.
       | 
       | "OOP is a tool to solve a particular type of problem. It is your
       | responsibility to know the tool, to understand its strengths and
       | weaknesses and when it is applicable and when it is not. If the
       | tool does not work it is not the tool that is faulty, it is you
       | who are the problem -- by using the tool in a wrong situation or
       | incorrectly."
       | 
       | In particular I detest "we are OOP shop" type of approach. This
       | immediately advertises they have absolutely no idea how to use
       | stuff -- by saying you know only one tool and sure you are going
       | to use it to solve every kind of problem.
       | 
       | Those languages that were supposed to be "everything is an
       | object" like Java? Now are learning that maybe that is not the
       | most sound approach and trying to evolve to allow other paradigms
       | under one roof.
        
         | throwaway894345 wrote:
         | This is boilerplate that can be used to defend any idea. First
         | of all, the article is trying to explain a domain for which OOP
         | is not well-suited. Secondly, it's unhelpful to write-off this
         | article with "there _are_ places for which OOP is well-suited"
         | without any specifics about when it is the better approach,
         | especially how it compares and contrasts with other approaches.
        
           | lmilcin wrote:
           | What I object to is this "OOP is good / bad" type of
           | approach.
           | 
           | OOP is neither good nor bad. What is good or bad is your
           | selection of technique for the problem at hand.
           | 
           | That's the same misguided discussion as on whether strong
           | typing is good or bad. It is neither. What you want to select
           | depends on the kind of project you are trying to use it for.
           | 
           | Also, just because this template can be used for practically
           | any idea doesn't make it less valid. Greatest laws tend to
           | have universal applicability.
        
             | throwaway894345 wrote:
             | > Also, just because this template can be used for
             | practically any idea doesn't make it less valid. Greatest
             | laws tend to have universal applicability.
             | 
             | I don't think it's invalid, but useless. "There's a time
             | and a place!" is not very enlightening. It doesn't help
             | anyone understand when to use it nor does it indicate how
             | frequently it is helpful (e.g., in every application or
             | only very rarely?).
             | 
             | > That's the same misguided discussion as on whether strong
             | typing is good or bad. It is neither. What you want to
             | select depends on the kind of project you are trying to use
             | it for.
             | 
             | Isn't this a straw man? TFA illustrates in detail the
             | problems with OOP in game development (e.g., performance),
             | so presumably for applications that care about those
             | things, then you have an idea about when it should be used.
             | This is _far_ more helpful than "but there's a time and a
             | place!" protestation or the "but OOP is neither good nor
             | bad!" protestation nor for that matter the "But that's not
             | _true_ OOP" protestation.
             | 
             | TFA substantially criticized OOP--if we can't rebut the
             | criticism _substantially_ , instead resorting to pithy
             | sayings, then maybe we should consider the criticism more
             | carefully?
        
               | lmilcin wrote:
               | I have not criticized TFA's arguments, only general
               | approach.
               | 
               | You can be correct with details yet completely wrong
               | about overall findings.
               | 
               | Of course abuse of OOP is causing memory layouts that are
               | bad for performance. But you can't jump from this to
               | saying that OOP is bad.
               | 
               | I will give you an example to show how absurd is this
               | argument.
               | 
               | Python is order of magnitude more damaging to app
               | performance than OOP. Surely, that must mean that "Python
               | is bad" and projects should not be using it.
               | 
               | This is absurd, invalid way of coming to conclusions.
               | 
               | > instead resorting to pithy sayings, then maybe we
               | should consider the criticism more carefully?
               | 
               | I don't care much about insults, certainly not the ones
               | coming from anonymous people.
               | 
               | A lot of contemporary bloggers were not even alive when I
               | started working in development and I seen generations of
               | people making same mistakes.
               | 
               | This is discussion you don't solve by getting more into
               | details but rather zooming out to understand general
               | truths.
        
               | throwaway894345 wrote:
               | > Python is order of magnitude more damaging to app
               | performance than OOP. Surely, that must mean that "Python
               | is bad" and projects should not be using it. This is
               | absurd, invalid way of coming to conclusions.
               | 
               | It is, but I don't think anyone is coming to this
               | conclusion by way of this line of reasoning. Rather, the
               | conclusion one can arrive at is that Python is bad for
               | performance sensitive applications.
               | 
               | That said, if no one can articulate a class of
               | applications for which OOP is indeed beneficial, but
               | rather only says "but there's a time and a place!"
               | without asserting those times and places specifically,
               | then one can reasonably conclude that OOP likely isn't a
               | very good paradigm.
               | 
               | > Of course abuse of OOP is causing memory layouts that
               | are bad for performance
               | 
               | It seems to me that the performance criticism holds for
               | just about any code base that is discernibly "OOP". It
               | also seems to me that for any term that is so poorly
               | defined as "OOP", someone could defend that term from any
               | criticism by arguing that the criticism applies only to
               | _abuses of_ the term. In other words, a no true Scotsman
               | deflections.
               | 
               | > I don't care much about insults, certainly not the ones
               | coming from anonymous people.
               | 
               | I wasn't making an insult, I was claiming/observing that
               | your argument lacks substance, that it's empty rhetoric
               | that can be used to defend any position. I don't mean it
               | as an affront to you personally.
               | 
               | > This is discussion you don't solve by getting more into
               | details but rather zooming out to understand general
               | truths.
               | 
               | I don't think you're observing "general truths" but
               | rather making pithy statements (again, I mean this
               | literally). In particular, you're arguing that there are
               | use cases for OOP without proposing any, and now you're
               | arguing that we don't demonstrate OOP's utility by
               | demonstrating use cases but rather by "zooming out"
               | (presumably to vapid rhetoric). It seems to me that I
               | could say that Bigfoot exists, and on being asked for
               | evidence, I would just argue, "you don't prove Bigfoot's
               | existence by way of evidence" or similar. How can anyone
               | in good faith interpret this as anything other than a
               | dodge?
        
       | 0xbkt wrote:
       | What is "flat" codebase, if you don't mind? Is it opposite to the
       | hadouken-style nested code?
        
       | jayd16 wrote:
       | Why are we always so dogmatic?
       | 
       | Struct of Arrays is a great pattern if you need the speed and
       | have enough items to make it worth it. Otherwise just use OOP.
       | Are you going to make an array of singleton data? No, of course
       | not.
       | 
       | >How many places in the code did you have only one of something?
       | 
       | One socket, one host, one pool, and assuming a single player
       | game, there are a lot of ones...
       | 
       | Just relax and use the strengths of both patterns.
        
         | gambiter wrote:
         | > Just relax and use the strengths of both patterns.
         | 
         | That's exactly what he says in the conclusion...
        
           | jayd16 wrote:
           | Agreed. I'm saying we as in the HN comments and to some
           | degree programmers in general.
        
       | axilmar wrote:
       | There is some middle ground between data-oriented design and OOP:
       | just organize your objects in such a way that:
       | 
       | a) objects of the same type occupy continuous blocks in memory,
       | 
       | b) messages are passed to objects of the same type, then to
       | objects of another type etc.
       | 
       | In this way, you don't lose the advantages of encapsulation,
       | inheritance, polymorphism etc but you also don't sacrifice cache
       | coherence much.
       | 
       | OOP does not enforce a 'random' memory access order, you can very
       | will organize your objects in such a way that speed is not
       | sacrificed much.
        
         | bruce343434 wrote:
         | > objects of the same type occupy continuous blocks in memory,
         | 
         | Depending on the language, a single object may have a lot of
         | overhead that adds up in an array. What you often see is one
         | ArrayObject with arrays of properties, kind of like a
         | transposition.
         | 
         | A problem there is that in memory the arrays are of course laid
         | out one after the other, which actually destroys cache locality
         | if you need to access more than 1 property inside a loop (it
         | will need to load back and forth to the different property
         | arrays), so it's a somewhat dumb approach. But, at least it
         | saves the overhead, so maybe not too bad. And in a high level
         | interpreted language like php you likely weren't gonna get
         | cache locality anyway.
         | 
         | The point is to group all properties you are going to be
         | accessing in a hot loop together in a small-ish array.
         | 
         | C has structs for this, 0 overhead "entities" (although they
         | may be padded to multiples of 4 bytes, so keep that in mind).
         | You have compiler specific keywords to forego padding ("struct
         | packing"), or maybe you're lucky and the data just fits exactly
         | right. Either way, in such cases an array of structs is imo the
         | most sane way to go.
         | 
         | In fact, C++ offers classes and structs. In my opinion, struct
         | should be used for entities like "weapon" or "car". CLASSES (or
         | objects) should be unix-philosophy adhering miniprograms that
         | do one task and do it well (oh hey, it's the single
         | responsibility principle!).
         | 
         | They way most programmers write OOP is a pretty convoluted way
         | to model actual entities anyway. car.drive()? Oh? The car
         | drives it self? No. agent.drive(car) should be the actual
         | method. Agent, mind you, can be a driving AI, or a human
         | driver, or whatever. Maybe the agent is a part of the car? In
         | that case, use composition, not inheritance. (oh hey, entity
         | component system!)
        
           | duped wrote:
           | > A problem there is that in memory the arrays are of course
           | laid out one after the other, which actually destroys cache
           | locality if you need to access more than 1 property inside a
           | loop (it will need to load back and forth to the different
           | property arrays), so it's a somewhat dumb approach.
           | 
           | This is actually why memory layout != DoD. You need to
           | account for this in the architecture of the program, so that
           | the systems only operate on a small amount of data that are
           | relevant to them at one time.
           | 
           | The tradeoff is paying for _all_ the data, _all the time_ ,
           | and some of the data most of the time. For a large class of
           | programs that can be architected around mostly non-unique,
           | trivially copyable fields with few relations, the tradeoff
           | between AoS and SoAs is obvious.
           | 
           | For other programs where your entities need relational
           | information and form trees or graphs, it can be less obvious
           | whether the data representation is going to be faster.
           | However in these cases you store the relationship as your
           | data (for example, as an adjacency matrix), but implementing
           | any kind of textbook algorithm over it is basically reverse
           | engineering pointers with indexes.
        
           | Firadeoclus wrote:
           | > A problem there is that in memory the arrays are of course
           | laid out one after the other, which actually destroys cache
           | locality if you need to access more than 1 property inside a
           | loop (it will need to load back and forth to the different
           | property arrays), so it's a somewhat dumb approach.
           | 
           | Caches are perfectly capable of dealing with more than one
           | stream of data (there are some very specific edge cases you
           | may have to consider), accessing multiple arrays linearly in
           | a loop is generally more efficient than accessing a single
           | array of structs when you don't use almost all the struct
           | elements.
        
             | moldavi wrote:
             | I've seen a lot of ECS implementations that store
             | components in hash maps, keyed by entity ID. They iterate
             | over one hash map in a linear way which is fast, but then
             | they do a bunch of slow lookups like GP is saying.
             | 
             | In those situations, GP's suggestions are wise.
             | 
             | If you can iterate over arrays in parallel like you say,
             | that's also a good approach.
        
               | professoretc wrote:
               | > They iterate over one hash map in a linear way which is
               | fast, but then they do a bunch of slow lookups like GP is
               | saying.
               | 
               | Usually all but the most simple "systems" will need to
               | access more than one component, which means you have a
               | choice between a) store component data in regular arrays
               | (and potentially waste huge amounts of space if
               | relatively few entities have those components) or b)
               | store component data in some kind of hash table (and then
               | you use cache locality for all but the "primary"
               | component of a system).
        
               | ajuc wrote:
               | There are tradeofs between regular arrays, sparse arrays
               | and hash maps in ECS - it's very similar in concept to
               | storage hints in relational databases, and similarly to
               | relational databases you can add indexes if needed.
        
           | skocznymroczny wrote:
           | OOP doesn't force you do do car.drive().
           | 
           | You can have an abstract agent class/interface with virtual
           | "drive(Car c)" method. The method would be overriden by
           | AIAgent, HumanAgent etc.
           | 
           | The car itself would have more basic behavior, such as
           | "accelerate()", "turnLeft()", "turnRight()"
        
             | orwin wrote:
             | Interface is the best way to do a clean job in this case.
        
             | jhgb wrote:
             | > You can have an abstract agent class/interface with
             | virtual "drive(Car c)" method. The method would be
             | overriden by AIAgent, HumanAgent etc.
             | 
             | Is that a convoluted way of saying "use multiple dispatch",
             | or am I reading it wrong?
        
             | temporama1 wrote:
             | Kill me now
        
             | bruce343434 wrote:
             | I think you are over engineering it. The way I imagine it:
             | 
             | Car {float throttle, float brake, float wheel} inherits
             | PhysicalObject {velocity, mass, position}
             | 
             | Agent.accelerate(Car c){ c.throttle++; }
             | 
             | Agent.drive(Car c){ ... accelerate(c); ... }
             | 
             | Human inherits Agent
        
           | jcelerier wrote:
           | > In fact, C++ offers classes and structs. In my opinion,
           | struct should be used for entities like "weapon" or "car".
           | CLASSES (or objects) should be unix-philosophy adhering
           | miniprograms that do one task and do it well (oh hey, it's
           | the single responsibility principle!).
           | 
           | Please no. The only thing that matters is the language rules
           | ; any non-computer-encodable arbitrary rule like this on top
           | of the language rules just causes an additional lava layer.
           | 
           | There is one difference between class and struct and it's
           | default visibility. Use one or the other according to which
           | causes less tokens to appear in your code
        
             | bruce343434 wrote:
             | Fair point
        
           | mytailorisrich wrote:
           | > _agent.drive(car) should be the actual method_
           | 
           | That's equally object-oriented so I don't see how OOP is a
           | nonsensical way to model. A sensible model is up to the
           | designer, not OOP.
        
             | bruce343434 wrote:
             | You're right. It's not OOP, but rather the way most
             | tutorials teach it (and most programmers write it).
        
             | ajuc wrote:
             | The reason OOP kills cache locality and multithreading
             | opportunities is walking the object graph depth-first
             | through nested method calls and pointers.
             | 
             | Doesn't matter if it's car.drive() referencing driver
             | through a private pointer or driver.drive(Car c) calling
             | methods on Car through the provided parameter - in both
             | cases you will jump from class Car to Driver and back and
             | then again for the next car and the next driver.
             | 
             | In real life the callstack is rarely 2-levels deep - I've
             | seen stacktraces that had hundreds of levels. So your code
             | will jump 100 levels down then 10 levels up then another 10
             | levels down, and so on, and then finally back up through
             | 100 levels of nesting only to advance to the next top-level
             | object and do the whole ceremony again for each of them :)
             | It boggles the mind when you think about it :)
             | 
             | When the object graph is big enough and doesn't completely
             | fit in cache this slows the code by orders of magnitude
             | each time you jump through the border.
             | 
             | And because dependencies are implicit and execution order
             | is accidental (and programmer doesn't actually know what
             | other execution orders would be correct) - you cannot
             | easily parallelize that code.
             | 
             | The alternative is to specify the dependencies explicitly,
             | split the data according to functions that use it not
             | according to metaphysical Classes where it belongs and walk
             | the data graph in levels - starting from the level that
             | doesn't depend on any other code being run, completing the
             | level first then going to the level that now has all
             | dependencies satisfied, and so on.
             | 
             | Of course there might be cycles that require special
             | treatment, but at least they are explicit so you won't
             | introduce them unless you actually have to.
             | 
             | End result is basically "relational programming". In case
             | of gamedev it's called Entity Component System.
        
               | mytailorisrich wrote:
               | The point I'm trying to make is that OO design principles
               | are one thing, how these are implemented by various
               | systems or languages is quite another.
               | 
               | OOP does not kill cache locality for the simple reason
               | that these are orthogonal concepts.
               | 
               | > _split the data according to functions that use it not
               | according to metaphysical Classes where it belongs_
               | 
               | Well, of course and as mentioned, picking the best model,
               | and thus the best object representation, for the job is
               | paramount. I read " _split the data according to
               | functions that use it_ " as " _come up with objects that
               | make the most sense for what you 're trying to achieve_",
               | not " _forget about OO design_ ".
        
         | corty wrote:
         | Only in C++. Most other OOP languages do not allow controlling
         | allocation that way.
         | 
         | Also, OOP only allows array-of-structs continuous data. Struct-
         | of-arrays and hybrid forms are usually awkward or impossible.
         | And with everything except maybe C++ and Rust, those "structs"
         | in OOP-land do have quite an overhead compared to C structs.
        
           | mytailorisrich wrote:
           | OOP does not say anything about memory allocation.
           | 
           | OO principles are one thing, what specific languages do is
           | quite another.
        
             | HelloNurse wrote:
             | But not destructuring objects into SoA memory layouts is a
             | "principle", since the availability of pointers to objects
             | is rather fundamental for all OO "specific languages".
        
               | jhgb wrote:
               | > since the availability of pointers to objects is rather
               | fundamental for all OO "specific languages".
               | 
               | I don't see how that is the case. For example some
               | implementations of Smalltalk used object tables, so there
               | were no "pointers to objects", just numerical object IDs.
               | The physical interpretation of such IDs could get very
               | arbitrary.
        
               | mytailorisrich wrote:
               | This is not specific to OO and is not an OOP principle.
               | As soon as you have data dynamically allocated in memory
               | and you start passing pointers to them around you have to
               | be careful.
        
               | corty wrote:
               | > But not destructuring objects into SoA memory layouts
               | is a "principle"
               | 
               | No, not really. There are languages that allow inside-
               | out-objects where the "object" is a tuple of (class,
               | index). The class holds a bunch of arrays containing each
               | object's properties at the index-position that the object
               | indicates. Totally destructured, yet holds all the usual
               | OO "principles" like implementation hiding, abstraction,
               | access via objects, etc.
               | https://metacpan.org/pod/Object::InsideOut
               | 
               | This is exactly what I meant with "there are no OO
               | principles". Noone has a clear-cut set of those and
               | almost anything can be made to fit some set of "OO
               | principles".
        
             | corty wrote:
             | There are no real OO principles. Ask ten people and you
             | will get ten different answers. OO is defined by the
             | languages and tools claiming to implement it, and the set
             | of principles derived from those is inconsistent and
             | contradictory.
        
               | mytailorisrich wrote:
               | It's obviously not so.
               | 
               | There _are_ OO principles, which are indeed well-known,
               | and each OO language has its own take on how to implement
               | them.
               | 
               | It's not even needed to use an OO language to follow OO
               | design principles. My day job is pure C and we follow OO
               | principles as much as practical.
        
               | jerf wrote:
               | You conspicuously don't actually _name_ any OO
               | principles. If you did I 'm sure we could find "OO"
               | languages that don't conform to them.
               | 
               | My personal definition of OO has been backed down to
               | directly connecting some concept of "method" to a data
               | structure, and _some_ form of polymorphism of those
               | methods depending on what data structure you pass in to
               | some function /method. You may note this is incredibly
               | weak, but it does have the virtue of usefully
               | distinguishing between two sets of languages, and that
               | those two sets will have real differences in how you
               | program them. Beyond that it's hard to create a
               | definition of OO that has the second property; you may be
               | able to split the world into "languages that implement OO
               | visibility rules (private, protected, public) and those
               | that don't", but you'll fail the second criterion, in
               | that languages that just leave everything public aren't
               | meaningfully different to program in than ones that
               | implement the visibility rules.
               | 
               | I could create several different sets of "OO principles",
               | which wouldn't be mutually exclusive necessarily but
               | certainly would be distinguishable. Especially the
               | distinction between the silly principle that OO objects
               | should somehow reflect real-world entities, which was the
               | major failure in 1980s/1990s OO principles and has,
               | mercifully, all but died in the modern era but _most
               | certainly_ was at one point an  "OO principle", and any
               | of the several sets of OO principles I could name that
               | actually function in the real world.
        
               | mytailorisrich wrote:
               | Well, this is HN so I did not want to sound condescending
               | by stating the obvious.
               | 
               | OO Programming 101, OO principles: encapsulation,
               | abstraction, inheritance, polymorphism, SOLID.
               | 
               | Whatever a specific language adheres to and how is beside
               | the point.
        
               | jerf wrote:
               | The tree under your reply proves my point. There is no
               | one set of OO principles. This thread identifies at least
               | two, the original Kay principles and what I wasn't sure
               | you were going to name, which is what I'd call the
               | outdated 1990s ideas of OO. Then there's today's idea,
               | which is probably pretty close to what I said in my post
               | and is exemplified by duck-typed dynamic languages and a
               | lot of modern languages like Go and Rust. That's at
               | _least_ three, and that 's staying fairly broad; if we
               | start quibbling about arcane details the count only goes
               | up.
        
               | mytailorisrich wrote:
               | > _what I wasn 't sure you were going to name, which is
               | what I'd call the outdated 1990s ideas of OO._
               | 
               | I'm sorry but this is getting surreal.
               | 
               | I named the standard OO principles and concepts which are
               | very much valid and alive today, though of course how
               | they are applied (or if they are applied at all) varies
               | from language to language. Claiming otherwise is absurd.
               | If anything this whole article and thread show that too
               | many people are confused by the concepts of OO principles
               | (if they know what that means at all), programming
               | languages (that may or may not implement some of these
               | principles), design practices/patterns (how to come up
               | with a model of objects): these are all different things.
               | Certainly selecting objects that reflect real-life
               | entities is not an OO principle, for instance, but rather
               | a design practice (good or bad, it depends).
               | 
               | In my team we do C exclusively and follow OO principles
               | as much as practical. Any software engineer worth their
               | salt has a good idea of what that means.
        
               | bccdee wrote:
               | Those aren't exclusive to OO, though.
               | 
               | C, for all its faults, has encapsulation at a module
               | level: any functions you don't define in your header file
               | aren't exported and are thus private. Go and rust do the
               | same thing.
               | 
               | Abstraction is even more common. _Functions_ are
               | abstractions. And any language with typeclasses (like
               | haskell) or function overriding (like Julia) uses a form
               | of polymorphism
               | 
               | Really, the only essentially object-oriented things here
               | are inheritance and (by extension) inheritance-based
               | polymorphism.
        
               | rsj_hn wrote:
               | > C, for all its faults, has encapsulation at a module
               | level:
               | 
               | No language that supports memory access for the entire
               | address space of the currently running program can ever
               | support something like encapsulation: you can pass
               | pointers to objects and functions outside the currently
               | running module, or you could somehow derive this info
               | from outside the module and so access functions and
               | objects that were not declared in header files. Thus the
               | language cannot give you the isolation guarantees that
               | memory managed languages can. What it can do, is put up
               | some roadblocks or barriers that require effort to cross.
               | But there is a big difference between correctness
               | guarantees and roadblocks.
               | 
               | There really is a qualitative change when you are working
               | in a memory managed language as that allows the language
               | to assign fine grained control over which memory
               | addresses are available to which data structures, which
               | is something that you cannot do with C.
        
               | corty wrote:
               | Well then, C++ doesn't have encapsulation, therefore C++
               | isn't OO. Hell, even Java isn't OO if you allow JNI.
        
               | bccdee wrote:
               | If we're talking about making guarantees about blocking
               | the programmer's ability to modify parts of the address
               | space, we're no longer discussing programming paradigms.
               | We're discussing security proofs. The MMU does not play a
               | core role in object-oriented programming.
        
               | corty wrote:
               | Historically, this is not entirely correct. Segmented
               | MMUs (as opposed to the more common, currently used
               | concept of paged MMUs) were intended to provide the
               | hardware support for the protection levels and the
               | data/code mixture in OOP. I.e. each object would have
               | executable, readable, r/w and inaccessible parts.
               | Protected by the MMU, depending on the currently
               | accessing context, that is, a subclass, friend class,
               | other class, etc. But creating a segment descriptor for
               | each object or even just each class was, of course, far
               | too expensive in the end.
        
               | mytailorisrich wrote:
               | OOP is a set of principles. C is a language. These are
               | not the same things.
        
               | bccdee wrote:
               | "Abstraction" as a principle is something we've been
               | doing since we came up with function calls. Encapsulation
               | as a principle is something we do when writing C code.
               | The only one of the listed OO principles which is in any
               | sense exclusive to OOP is inheritance.
        
               | mytailorisrich wrote:
               | > _Encapsulation as a principle is something we do when
               | writing idiomatic C code._
               | 
               | That's clearly not the case. C obviously does not enforce
               | encapsulation, and it's extremely common for devs not to
               | follow this principle, in fact it's pretty much the
               | default not to and it takes discipline to enforce it.
               | 
               | " _Encapsulation at module level_ ", as you wrote
               | earlier, is not encapsulation. If you implement your
               | object as a struct (which is really what objects are)
               | then encapsulation means not accessing the content of
               | that struct/object directly.
        
               | wtetzner wrote:
               | ML supports encapsulation via modules and abstract types.
        
               | corty wrote:
               | Then OOP is no true scotsman, because no language
               | implements all the principles.
               | 
               | Or in other terms, without an implementation, OOP isn't
               | even usable, it isn't even real. Just maybe a desirable
               | ideal somewhere.
        
               | jhgb wrote:
               | That must be some kind of "new OOP", since the "old OOP"
               | is messaging, local retention, and protection and hiding
               | of state-process, and extreme late-binding of all things.
               | At least according to Alan Kay, who wrote this verbatim.
               | 
               | To wit, encapsulation and abstraction existed outside of
               | OOP (for example, Modula had it before), inheritance is
               | not a necessary feature for OOP (Self doesn't have it),
               | and the O in SOLID doesn't apply to Smalltalk and Self.
        
               | mytailorisrich wrote:
               | That's standard OOP as it stands today (versus the 60s
               | when Alan Kay coined the term).
               | 
               | Alan Kay considers that inheritance and polymorphism are
               | not essential, fine. He does consider encapsulation
               | essential, though. Specific languages have their own
               | take, fine.
               | 
               | The point being is that there are well-known OO
               | principles. Claiming otherwise is either disingenuous or
               | ignorant.
        
               | jhgb wrote:
               | > versus the 60s when Alan Kay coined the term
               | 
               | It was in the 70s, and his description that I quoted is
               | from the 2000s.
               | 
               | > Alan Kay considers that inheritance and polymorphism
               | are not essential, fine.
               | 
               | Polymorphism is a logical outcome of his requirements. So
               | in a purely logical sense it is essential, although I
               | imagine that saying that might be a little bit like
               | saying that CO2 is essential for a campfire (as in that
               | you can't get a campfire without emitting CO2, even
               | though that is strictly a matter of consequences).
               | 
               | > He does consider encapsulation essential, though.
               | 
               | Yes, because biological cells are encapsulated.
               | 
               | > there are well-known OO principles. Claiming otherwise
               | is either disingenuous or ignorant.
               | 
               | There surely are some "well-known principles" but whether
               | the "known" in that phrase has the same meaning as in
               | "knowledge" (justified true belief at a first
               | approximation) seems debatable.
        
               | simiones wrote:
               | I think that, while most people can't really articulate
               | this well enough, there is a pretty good common
               | understanding of what style of programming is OO: it's a
               | style of programming where code is quite deeply tied to
               | data, especially modifications of persistent state
               | (encapsulation), and where subtyping is commonly used to
               | model program behavior (interfaces, inheritance, virtual
               | dispatch, polymorphism).
               | 
               | This would mostly contrast with procedural code, where
               | code and data are much more separate - procedures often
               | manipulate and pass around complex data structures -, and
               | subtyping is not commonly used for program behavior;
               | instead, flow control is usually explicit (e.g.
               | switch()'ing on an enum value).
               | 
               | It is also commonly contrasted to Functional Programming,
               | where data is also loosely tied to code, with functions
               | often reading (but usually not modifying) deep parts of
               | complex data structures; and where higher order functions
               | and sum types are used to achieve dynamic dispatch.
        
         | inopinatus wrote:
         | Quite so. There's a false equivalence in this article between
         | data and encapsulated state, but if that were so then the
         | flyweight pattern and its ilk couldn't exist.
        
         | ajuc wrote:
         | This is kinda what Entity Component Systems do - they implement
         | in-memory relational database for game objects, handle
         | dependenceis and allow your game logic code to run efficiently
         | over them while still keeping the pretense of OOP :)
         | 
         | Why pretense? Because behaviors (Systems in ECS terms) are
         | completely separated from data (Components) and data for
         | different game objects (Entities) is kept together in regular
         | or sparse arrays.
         | 
         | Encapsulation is nowhere to be seen, code is written to specify
         | the components it depends on and run on these arrays.
         | 
         | ECS is very fashionable in gamedev lately as it allows for
         | efficient multithreading, explicit depencencies for each
         | subsystem, cache locality and trivial (de)serialization. Used
         | together with handles (tagged indexes instead of direct
         | pointers) it reduces likelihood of dangling pointers and other
         | memory management bugs.
        
           | tffgg wrote:
           | ECS ist Standard for enterprise web apps as well
        
             | sdeframond wrote:
             | I am curious as to what you are referring to. Are you
             | thinking of redux-like architectures?
        
             | tompazourek wrote:
             | I have seen some enterprise web apps, but they never used
             | ECS. Can you please share more details about your
             | experience?
        
         | megameter wrote:
         | If we are speaking of C code, it's not quite so bad as it looks
         | to have somewhat fat structs across multiple arrays, since you
         | can fit 64 bytes in a cache line on contemporary desktop CPUs,
         | and that sets your real max-unit-size; the CPU is actively
         | trying to keep the line hot and it does so (in the average
         | case) by speculating that you're going to fetch the next index
         | of the array. Since you have multiple cache lines, you can keep
         | multiple arrays hot at the same time, it's just a matter of
         | keeping it easy to predict fetching behavior by using simple
         | loops that don't jump around...which leads to the pattern
         | parent suggests, of cascading messages or buffers in groups of
         | same type so that you get a few big iterations out of the way,
         | and then a much smaller number of indirected accesses.
        
           | volta83 wrote:
           | If you loose vectorization, you might be loosing a 4x, 8x,
           | 16, ... 32x perf difference by organizing your data in such a
           | way that memory operations and data manipulation can't be
           | vectorized.
        
             | moldavi wrote:
             | When you say vectorize, are you referring to loop
             | unrolling? Or SIMD or something?
        
               | simiones wrote:
               | I have never heard vectorization to refer to anything
               | other than SIMD. Loop unrolling is usually only a useful
               | technique to enable SIMD, as far as I know (at least on
               | modern processors, where branch prediction has greatly
               | decreased the cost of jump instructions).
        
               | jhgb wrote:
               | > as far as I know (at least on modern processors, where
               | branch prediction has greatly decreased the cost of jump
               | instructions).
               | 
               | What about ILP? Can't that benefit from an unrolled loop
               | in some cases? For example if there's a fairly long
               | dependency chain but you might still be able to go
               | through two loop bodies at once instead.
        
               | tsimionescu wrote:
               | I don't know for sure at all, but I don't think it's
               | impossible that speculative execution could also achieve
               | the same at the processor level.
        
               | jhgb wrote:
               | I don't see how this has anything to do with speculation?
               | In most cases where you care about this you don't have to
               | speculate if all the loop iterations are needed. For
               | example in matrix multiplication all of those iterations
               | will be needed.
        
               | tsimionescu wrote:
               | What I'm thinking is that the processor has an
               | instruction stream that looks like this:
               | loop:          instr_1         instr_2         ...
               | instr_n          jcond loop
               | 
               | Now, assuming the loop is not unrolled, it would need to
               | speculate that `jcond loop` will jump to be able to
               | execute 2 copies of instr_1 in parallel - I'm saying that
               | it _may_ be able to do that, though I am by no means
               | sure.
        
               | jhgb wrote:
               | Oh, I see what you mean -- I was talking (and thinking)
               | about the unrolled version so it didn't make sense how
               | speculation could help there. But I imagine that
               | typically the kind of long chains that you might want to
               | do in parallel in a single basic block are perhaps
               | something that wouldn't get executed _that_ far after a
               | branch, if the only purpose is to not waste time after a
               | branch misprediction. Plus from what I understand you 'd
               | still be wasting execution units here, just not by idling
               | them but rather by speculating the "I'm done" branch
               | repeatedly.
               | 
               | EDIT: I just found that the idea that I had in my head
               | actually exists and is called "modulo scheduling".
        
             | lasagnaphil wrote:
             | But you usually can't achieve vectorization by just simply
             | changing your data layout, the compiler's auto-
             | vectorization features usually doesn't work that well. SOA
             | or AOSOA layout for vectorization only becomes important
             | when you begin to explicitly write SIMD code in intrinsics
             | or pure assembly.
             | 
             | And explicitly writing in SIMD is quite a hard feat in
             | itself: it's okay when you're accelerating small, simple,
             | and isolated algorithms in hot-code paths, but when you're
             | doing much more complex calculations the time you need to
             | invest in it to make it work goes out of hand pretty
             | quickly.
        
         | physicsguy wrote:
         | I find in simulation codes that lack of awareness of (a) is an
         | absolute performance killer. Generally, it's better to use a
         | pattern for an object that's a container for something - so
         | don't have a 'Particle' object but a 'Particles' one that keeps
         | things stores the properties of particles contiguously. In my
         | old magnetics research area you have at least 8 and more
         | frequently 10+ spatially varying parameters in double precision
         | that you'd potentially need to store per particle/cell.
        
         | fennecfoxen wrote:
         | You might be looking for "Entity - Component - System" design,
         | common in video games. Entities are still virtual-world objects
         | like you might expect, but none of them would dare keep track
         | of something like their position or temperature or whatever.
         | Instead, they register a component with the appropriate system,
         | which keeps all the data colocated for efficient physics and
         | the like.
        
       | dgb23 wrote:
       | One thing that is interesting about this, is that people
       | sometimes end up building an (incomplete) implementation of
       | relational algebra to achieve this, where any given system in the
       | game logic pipeline might join over multiple components.
        
         | epage wrote:
         | Thats what made ECS click for me: its an in-memory, natively
         | typed database with a feedback loop.
        
       ___________________________________________________________________
       (page generated 2021-06-28 23:02 UTC)