Want Sanity? Pass by Value

by Nox

on 07.15.12

My beard is kept mutually exclusive of my neck, and I've never read a language standard if I could help it. The esoterica of programming languages, often held in holy reverence by so called “experts,” (who I'm convinced never get anything done,) clouds my vision and leaves me feeling unaccomplished and subhuman. I'm not an “expert”, and I don’t claim to be. I have however, been rather tormented by my attempts over the past several years to implement a component based entity system that was anything more than cumbersome, inconsistent, and difficult to scale. Component based entity systems, and video games in general, seem to revolve around and depend upon shared mutable state. Doubtlessly, mental models involving shared mutable state are what come most naturally to someone with a typical OO mindset of objects having unique identity and mutating themselves on demand with no regard for what far reaching effects that may have. Much to the chagrin of imperative programmers the world over, shared mutable state is at the core of what makes concurrency so difficult, not to mention the problems it poses for code cleanliness and verifiability. A little research into functional programming, specifically pure functions and referential transparency, will do enough to convince one of the virtues of immutability, though one must first wade through a sea of aggrandized academic bullshit to get at what really matters. Even then, you may be left wondering what this means to you in C++ (or whatever).

It means pass by value.

The virtues of passing by value are widely documented, although apparently infrequently practiced by developers of the most popular libraries and frameworks, and thus stigmatized and misunderstood by the vast majority. Pass by value should be the rule, not the exception. This is made abundantly clear by the overwrought and top-heavy solutions that aim to make pass by reference amenable to multithreaded environments. The difficulties of multithreading stem largely from the complexities of data sharing, and specifically the mutability and synchronoization of that shared data. Controlling this mutability in the traditional manner, tainting functions with locks and mutexes far beyond their purview, and continuing to endanger data integrity outside the scope of human comprehension, requires utmost knowledge of a codebase’s inherent side effects and a masochistic desire for undue pain.

If everything were pass by value, a load of meaningful mutability would move to a higher level of the problem space, where it could be much more easily controlled. At this higher level, groups of functions can be freely composed, partitioned, and run using whatever concurrency mechanism you'd like. Internally, functions may optionally distribute their workloads further, perhaps using facilities provided OpenMP or TBB. Our goal isn’t to destroy mutable state—it’s critical to any non-trivial program—but to minimize it, and like monads in Haskell, make its existence apparent.

It’s not all poops and ladders, however. Passing by value is somewhat complicated by the OO party line, in support of data-filled classes wedded to self-mutating, and certainly impure, member functions. In fact, OO is antagonistic to pass by value in general, which is one of the reasons for my recent retreat from the punch bowl. Inheritance based polymorphism gives one no choice but to pass by reference, lest they encounter slicing, and lose all of the purported benefits of runtime polymorphism. Personally, I've lost faith in the benefits, and that’s a post for another day.