Live data from Hacker News

Revisiting the principles of data-oriented programming

blog.klipse.tech

101–110 of 117 posts

Re: Revisiting the principles of data-oriented programming

#101
post #40

Earlier quoted context omitted.

Because the problem in OO is if you have any kind of cross-cutting concern, then it collapses totally. For example, I have a Wizard object. My wizard has a wand, we store the wand object on our Wizard object. Simple. But then my wizard casts a spell, and does damage to a goblin. Do we put the cast method on the wand, the wizard? There is no real reason to pick one over the other (this problem comes up a lot with game…

>But then my wizard casts a spell, and does damage to a goblin. Do we put the cast method on the wand, the wizard? There is no real reason to pick one over the other You did pick: "My wizard casts a spell". The cast method goes on the wizard.

Because the function of the spell is to modify the state of the wizard, the wand, and the goblin...that isn't obvious or correct. It can go on the wizard, it can go on the wand but the problem is that the solution is very brittle (because you will likely end up with inheritance as you add other objects). Again, the key point is that in OOP these cross-cutting concerns will likely end up with a design that is complex/arbitrary/unperformant.

This is a good tutorial on this topic - https://ericlippert.com/2015/04/27/wizards-and-warriors-part...

Re: Revisiting the principles of data-oriented programming

#102
post #85

I believe the easiest way to think about it is to get away from your programming tools and to start modeling your problem domain as tables in excel. Once you have a relational schema that the business can look at and understand, then you go implement it with whatever tools and techniques you see fit. This is what “data-oriented” programming means to me. It’s not some elegant code abstraction. It’s mostly just a proce…

This confused me as when I hear data-orientated I think data structures that are optimised around minimising CPU cache misses by using better alignments, using enums where possible, not storing results of simple calculations etc. There is a popular book and popular talks on the subject. Probably confuses other people as well I'd imagine

> I think data structures that are optimised around minimising CPU cache misses by using better alignments, using enums where possible, not storing results of simple calculations etc.

You might be surprised to learn that modeling your problem in terms of normalized relational tables ultimately achieves similar objectives. The more normalized, the more packed you will find their in-memory representations.

Re: Revisiting the principles of data-oriented programming

#103

Earlier quoted context omitted.

It's not impossible to type check heterogeneous maps at compile time, but most static type systems don't support this. I think you'd certainly see much more friction trying to program like this in C# than you would in Clojure.

> It's not impossible to type check heterogeneous maps at compile time, but most static type systems don't support this I guess you mean dependent types[1], but if you don't, I'd appreciate an elaboration. If you do mean DTs, how might it look for a hetero collection? [1] If anybody has any good intros to dependent typing in C#, that'd be much appreciated. A web search throws up some pretty intimidating stuff.

siknad and uryga give good replies on dependent types and TypeScript's structural typing. There's also an experimental static typing system for Clojure that allows for typing of maps:

    (defalias NamedMap
      (HMap :mandatory {:first-name Str, :last-name Str}))

    (ann  full-name [NamedMap -> Str])
    (defn full-name [{:keys [first-name last-name]}]
      (str first-name " " last-name))
If you can determine some subset of the keys of a map at compile type, then you can type check it to some degree.

Re: Revisiting the principles of data-oriented programming

#104

Truly misguided article as most of the things by the author, unfortunately. I was so excited about the book "Data-oriented programming" when it was first being released...it was so heavily publicized as well that it was constantly in my face which likely pushed me over the edge to give it a shot and buy it. Unfortunately, not all that glitters is gold. It feels extremely beginner oriented, only touched basic concepts…

how did it show a disconnect?

Essentially, by pushing the discussed topics as the One True Way and almost "purposedly" choosing to not discuss trade-offs and talking about alternatives or disadvantages. In the real world, trade-offs are very important

Re: Revisiting the principles of data-oriented programming

#105
post #76

Earlier quoted context omitted.

The goal of OOP or any programming system is to help you enforce invariants to improve correctness of a program. “has String members ‘firstName’ and ‘lastName’” is a perfectly reasonable invariant that isn’t all that well served by traditional OOP. You can’t strictly enforce the invariant I just stated via OOP. You can only define tangentially related concepts via inheritance.

> You can’t strictly enforce the invariant I just stated via OOP. but you can strictly enforce it in pretty much every relevant OOP language - Java, C#, C++, C, Rust, D, ... by defining a class / struct / record with these two members, which is the only thing that matters. No one programs in abstract design principles, only in actual programming languages.

Ok, show me in C++. You have these three structs, which you cannot alter the signature of (maybe from an external library, maybe you don't want to introduce a type hierarchy in someone else's code, etc).

  struct Name {
    std::string firstName
    std::string lastName
  }
  struct AnotherName {
    std::string lastName
    std::string firstName
  }
  struct YetAnotherName {
    std::string middleName
    std::string lastName
    std::string firstName
  }
Write a single function that returns firstName and lastName concatenated. Bonus: write it for any struct containing firstName and lastName. The only way I can think to do it is via templates, which aren't traditional OOP and have their own downsides. Concepts in C++20 look like they make this much easier, but, again, not expressed via traditional object orientation and still infects your code with templates.

This isn't theoretical. I often don't want and often cannot use inheritance-based polymorphism. If I'm using a language where that is the only option, I'm struck writing tons of redundant, error prone, pointless, and brittle glue code. The amount of glue explodes combinatorially. That glue code can contain errors that the type checker won't find.

The inverse of this problem is also interesting. Someone wrote a function to concatenate the strings in Name. I can't put AnotherName into it unless the original author had the forethought to make their function templated. I guess the future of C++ is that all code ever lives in headers.

Re: Revisiting the principles of data-oriented programming

#106
post #96

Earlier quoted context omitted.

i haven't used scala, but from the looks of it, yeah, "path-dependent types" are a narrow subset of full dependent types, intended for stuff like this exact use case :D there's things you can do to track list length at the type level, but it usually involves putting your data in a special-purpose linked-list thingy: https://docs.idris-lang.org/en/latest/tutorial/typesfuns.htm... (the `S` and `Z` refer to peano-style…

Thanks, yeah, did a big search for DT stuff this morning and found this one. The S/Z is the zero/successor. I need to study it. Perhaps closer to what I originally saw was this https://gist.github.com/bradphelan/26c0e84197092620359a (edit: it's not closer. Still worth a peruse though) I need to sit and read and butt heads with these if I can find the time. It's so odd there are so few examples of C# DT. The one good…

good luck! it's quite fun :)

another term that might be useful is "GADT" - it's kinda like a weaker form of DT, more easily expressible in, uh, normal languages. the C# one i linked is really more like a GADT, bc it all stays at the type level w/o bringing "normal" values into it. another way to do it would be a sealed class with a private constructor + static methods:

  class Foo {
    private Foo(...)
    static Foo Bar() { ... }
    static Foo Zap() { ... }
  }
(or sth like that, i don't really do C#!)

so that way you can have the type param and use it to track something, but limit what can be put there - in this case, only int or string. type-safe syntax trees are a common case for sth like this (though in that case you'd probably go for an abstract base + subclasses, like in the link).

Re: Revisiting the principles of data-oriented programming

#107
post #105

Earlier quoted context omitted.

> You can’t strictly enforce the invariant I just stated via OOP. but you can strictly enforce it in pretty much every relevant OOP language - Java, C#, C++, C, Rust, D, ... by defining a class / struct / record with these two members, which is the only thing that matters. No one programs in abstract design principles, only in actual programming languages.

Ok, show me in C++. You have these three structs, which you cannot alter the signature of (maybe from an external library, maybe you don't want to introduce a type hierarchy in someone else's code, etc). struct Name { std::string firstName std::string lastName } struct AnotherName { std::string lastName std::string firstName } struct YetAnotherName { std::string middleName std::string lastName std::string firstName }…

> The goal of OOP or any programming system is to help you enforce invariants to improve correctness of a program.

enforcing invariants means reducing the number of types that satisfy the invariant. I wouldn't call doing what you ask for "enforcing" any invariant.

> Bonus: write it for any struct containing firstName and lastName.

well,

    auto concatenate(auto t) { return t.firstName + " " + t.lastName; }
satisfies your condition - here's one that'll also handle middle names: https://gcc.godbolt.org/z/YfTYs6MMn ; again I don't think anyone wants this when they say "enforcing invariants", this does exactly the opposite of what is actually wanted.

> I guess the future of C++ is that all code ever lives in headers.

or in modules, which makes it very similar than other languages with generics instantiation

Re: Revisiting the principles of data-oriented programming

#108

Reading the discussion here, I can't help but thinking that people are defending their own philosophies: OOP vs FP vs DOP vs etc. I wish the author had killer applications or killer examples in different categories, like can I code an operating system easier, can I code a database easier, can I create a complex streaming job easier, can I write a library as complex as Apache BEAM easier, can I write a compiler easier…

You'll have to define "OOP" first. Everyone thinks its defined, but even among OOP proponents there isn't consensus ("it's about message passing", "it's about encapsulation", "it's about inheritance", "it's about dot-method syntax", etc).

My point is that the author needs to make it clear what exactly DOP can do better for. OOP, whatever how that is defined, is just an example of what people discussed under the OP.

Re: Revisiting the principles of data-oriented programming

#109
post #75

Earlier quoted context omitted.

Multiple dispatch solves that question. I wish it was better known. Dispatch is on both wizard (or generally, user) and wand (or generally, item used)

Right but now it’s also raining and a full moon and the goblin is a werewolf and wands also have AOE spells that hit multiple opponents, except when those opponents are blocking… Sure, the code kinda sucks either way, but the data oriented approach works exponentially better as the object interactions become more complicated. A “cast” function called as part of the event loop can look up all the game state in the sta…

>Right but now it’s also raining and a full moon and the goblin is a werewolf and wands also have AOE spells that hit multiple opponents, except when those opponents are blocking…

>the data oriented approach works exponentially better as the object interactions become more complicated

Maybe it's just me, but I still don't see the difference. To use your example, you'd have a function like:

cast(caster, targets, weather, time, location)

or you can create an object and call it something like Spell and do something like:

class Spell

   def initialize(caster, targets, weather, time, location)
     ...
   end

   def valid_target?
     ...
   end

   def valid_caster?
     ...
   end 
end

Perhaps it's just my mental conception of things. My mental model of a class is a data structure plus a bunch of functions that implicitly take the data structure as a parameter. I realize that there can be more to it than that but it works for the purposes of this discussion.

Ultimately it's a code organization question either way. The original question is what class does it go in? Changing it to data structure + functions just changes the question to what module/file does the cast function go in? Maybe that's an easier question to answer but I guess I just don't see it.

Re: Revisiting the principles of data-oriented programming

#110
post #105

Earlier quoted context omitted.

Ok, show me in C++. You have these three structs, which you cannot alter the signature of (maybe from an external library, maybe you don't want to introduce a type hierarchy in someone else's code, etc). struct Name { std::string firstName std::string lastName } struct AnotherName { std::string lastName std::string firstName } struct YetAnotherName { std::string middleName std::string lastName std::string firstName }…

> The goal of OOP or any programming system is to help you enforce invariants to improve correctness of a program. enforcing invariants means reducing the number of types that satisfy the invariant. I wouldn't call doing what you ask for "enforcing" any invariant. > Bonus: write it for any struct containing firstName and lastName. well, auto concatenate(auto t) { return t.firstName + " " + t.lastName; } satisfies you…

Ahh you’re, right, I forgot about fully auto’d functions. I think want more structure than just auto everything, although the compilers should find mistakes with that. Concepts will be nice.
Post reply on HN