Hacker News new | past | comments | ask | show | jobs | submit login

I'm a bit sceptical about the ragging on global mutable state, given how useful it is (in my use cases, for resource management, e.g. connection pooling, caching, JIT compiling, file loading).

Global mutable state is akin to an external interface. The user is a global mutable state. If there is no user, then the external data the program is interacting with is often global mutable state (it may change in arbitrary ways, and your program should behave differently), a database is global mutable state, as is the DOM / UI.

So, beyond the 101-level advice (don't use global variables, prefer explicit state, reduce visibility, etc.), global mutable state is unavoidable, and it needs to be treated as 'external' by any code that uses it. I think that is the problem. Not so much global mutable state, but global mutable state with privileges, global mutable state that any bit of code makes assumptions about.

Program defensively against global mutable state. Reduce it, but don't be religious about it. It's still a tool in the toolbox if needed.




Connection pools, resource caches, file loads (a resource cache) have no need to be global. If you inject your dependencies (also known as partial application in functional languages) you will find it far easier to test things in isolation, to decouple modules, and to refactor your software.

The UI being global mutable state isn't actually true on all platforms (but is true on the web). And even then its possible to "hide" that to some degree in well designed frameworks.

Never say never, but half of your examples _shouldn't_ be globally mutable state.


One of us is using terms wrong, because we're talking about different things.

You seem to be talking about accessing things through interfaces, so that one can change whatever is on the other side of the interface. For testing, for example.

I am referring to something that there is only one of, while the program is running, which holds state, and that all part of a program potentially have access to.

Even if a dependency is injected, if it has state, and all parts of the program could potentially access it, I would consider it global mutable state.

This is part of the problem with the 'don't use global mutable state' advice. If what you really mean is 'access state through interfaces', then that's what we should say.

So, to be concrete, in my use of the term, a file cache is global mutable state because

a) I don't want multiple caches, potentially with the same content, in my program. In fact, this would be bad enough that I'd like to be confident it can't accidentally happen. I understand that some caches don't need this guarantee, but some do.

b) All parts of my program that use a file, use the cache to retrieve it.

c) The contents (and therefore the behavior, most specifically the timing) of the cache depends on the history of the program up to that point.

However, my implementation, I access files through an interface, so it would be trivial to change implementation.

Dependency injection does not insulate you from the global mutable state. It is a way of managing containers for state (or other behavior). Those containers can have any boundaries within your program you choose, from global down to the most local (remember that 'global versus local' is a continuum, not a binary).


> Even if a dependency is injected, if it has state, and all parts of the program could potentially access it, I would consider it global mutable state.

Not all parts of the program could potentially access it - it's injected where it's needed but only where it's needed, if other parts of the program wanted access to it you would have to change them so that it was injected there as well.


s/injected/imported

It makes little substantive difference whether the import is in code or in a separate declaration. Little substantive difference to whether the state is global and mutable, that is. Don't misunderstand me as claiming dependency injection is pointless.


You do not need interfaces for dependency injection and it can also be done without global mutable state.

This Clojure library is doing so beautifully with immutability: https://github.com/stuartsierra/component


> You do not need interfaces for dependency injection

Yet each component is supposed to implement component/Lifecycle protocol... in other words, an interface.

And component is actually awful in practice. It may look neat in theory, but it's too simple and results in a mess not much better than it would be without it.


Looking at my modern code on Github (not the stuff from the 1970s and 1980s I've revived), few projects have any global mutable variables at all. There are a few that are assigned once at startup. There's a timestamp. In Javascript work, of course, there's the DOM and other state that belongs to the browser.

In some other Python code, I have some global objects which manage some data, but few or none standalone global variables. There just doesn't seem to be much need.


I agree that going to the extreme of 100% eliminating global mutable state is going to fail for practical purposes, but we don't need to go that far to benefit.

I think of the code that I wrote when I first started programming. Nearly every variable was global, because I simply couldn't be bothered to think about where it could be accessed from. But nowadays I can't remember the last time that I used a global variable, and I'm no worse off for it. For beginners it's a tempting tool to turn to, and we need to continue teaching aversion to it. Like `goto`, we can both consider it harmful and also happily allow its use when well-considered.

(Historical fact: very ancient versions of Rust didn't have global state at all, as Graydon was hoping to find a way to avoid ever needing to use it; eventually he was persuaded otherwise. Though global mutable state in Rust still requires the `unsafe` keyword to access due to the risk of concurrent access, so it's rare to see it in the wild (and quite obvious when it is).)


I agree.

But here's the problem I have. If we don't want people to use global variables, the advice should be 'don't use global variables'. There is no need to wrap it up in 'avoid global mutable state', when using global mutable state is unavoidable in many cases and the best engineering solution in many others.

At the point you have to nuance it to 'reduce your code's exposure use global mutable state, accessing it through mockable interfaces, and defensively checking its values' to mean 'don't use global variables', it may be simpler to say what we mean.

For example: there is a comment lower down in this thread of someone confused by the advice in the context of the game engine. I've worked with hundreds of game engines, every one of them has global mutable state.


In many languages, variables are not necessarily state. Global variables are fine if they're global constants which can not (not just 'should not') be modified.


You really never use singletons or other static global objects? I use them fairly often, mostly in game dev.


Game dev is different than back-end server dev, is different from front-end web or desktop dev.

For example, in back-end Java dev, I never use singleton objects in the truest sense of the pattern; singletons cannot be mocked out to test units in isolation. Instead, instantiate it with a dependency injection container and let your framework (Guice, Spring) wire things together. In effect, your global state becomes contained within a single context. Nothing stopping you having two contexts in the same runtime environment though.

I find static methods are okay if they are concise and have no state; in particular if I'd never want to test the caller without it making the call to the static method anyway. This is becoming more important with Java 8's lambdas and needing to write helper methods.

On the other hand, the cost of doing this might not be worthwhile for a game dev? Do game devs write unit tests? I have no idea.


Game developers don't write tests. They pick up habits along the way and keep them for a long time. The code might be crap, but they know their crap very well and can debug it. There is less need for tests because most code never rarely refactored or being touched at all. It's already in the familiar style, why change anything?


When I started programming I remember just a mess of line numbers and GOTO statements in which there was no global variable at all... because literally everything was global. I like much more my code now with glorified singletons called IoC containers, extensions methods that are just global static methods and all the rest of the wagons... ...just joking, I'm trying to write more F# now to finally complete my journey procedural->object oriented->functional


> If there is no user, then the external data the program is interacting with is often global mutable state

Yes. Therefore you need to check every bit of data coming from user. You need to be ready to a lot of breakage, and need to code ways to recover for it. Working with external data is hard, need a lot of checking and exception handling. It is nightmare and you do not want to propagate this to every line of your code. So just do not create global state.

> a database is global mutable state, as is the DOM / UI.

And this also. You always need to expect something unexpected with global mutable state. DOM can be changed while your code is working with it. You should write code, that can notice such an inconvenience and be nice in this case. Or to write code that just works and do not breaks things, if something unexpected happened. Or code that makes unexpected impossible to happen. The same with databases.


Databases have proper context isolation and conflict detection (constraints, triggers) at least. You cannot break the invariants of a properly designed database (though some effort is required to create one).

I often miss transactions and checks on regular in-memory data. While it must be operative, and cannot be done out-of-process or even out-of-context, locks and dispatching is a mess at the end of the day, because correct paths are usually independent, and vice versa — conficting paths can simply fail or retry at the end, wasting cpu time, not developer's. Actually, good principles (i.e. explicit context, no globals, strict hierarchy, etc) are resembling transactions in a sense; you copy parts of state to worker(-s), delay updates into results and reintegrate these in dispatcher in a blocking manner. Only by hand.

One thing that stops me from doing everything in just sqlite is copying. You always copy to and copy back and every += becomes a copy operation. Right now (well for some background time) I'm thinking of the language that has the concept of transactions and relational algebra built right into the base types, but is not so cumbersome to write and/or poorly optimized like conventional database scripting. Mating rdbms internals with e.g. C-likes (as the common least) would be nice to test in real tasks.

Ps. I'm sure I'm missing some past info on that; if someone is aware of the details, you're welcome to share.


I previously worked on an extremely large (in whatever sense you wish to look at, size of codebase, number of users, transaction volume, whatever) system that was originally written in a language like you describe: Pro*C. It basically permitted in-lining SQL with your C code. It might've not been too bad on small systems, but shared Cs scaling problems. It was difficult to track where database changes were actually coming from, but I don't know of any other solution that would have made that easier. If you have a dozen applications across tens of millions of lines of code, all of which could potentially be responsible (or maybe whatever wrote the value they based their change off of was actually the problem...) things get hairy.

I am curious if you've looked at C# and their LINQ stuff though as it sounds like it might check a lot of those boxes. I'm no C# expert myself, haven't used it for several years (since right before LINQ was introduced) so I'm not sure, but it looked to be along those lines when it was being proposed.


Integrated language+database solutions are a recurring motivator for new one-off languages: Pick System and FoxPro are classic examples of writing around a custom language vs "scripting" inside SQL. Applications with custom database-like systems such as game engines also frequently lean in this direction(UnrealScript, GDScript). The main difficulty is in the practicals of getting all of the desired functionality into a useful package - not the theoretical aspects.

As of right now (for game projects) my strategy is to use source code generation to make the working language behave in a database-like fashion and emit a static result, rather than to engineer a complete dynamic schema with a scripting runtime and all the bells and whistles of a standalone database. The latter is both harder to build and lets me leverage fewer of the tools of the host environment.


The global mutable state isn't the problem, of course, it's any of your code that uses it. Minimize that code and you're done. And yes you are correct about all of the things you referred to as 'global mutable state'. They all are exactly that. They are very useful. People use software to do changes to that global mutable state, and that global mutable state is literally the only reason the software exists in the first place. It is the primary responsibility of software to manage it. And as with anything you need to care for, the less you have to manipulate it the better off everyone will be.


Basically you want to push global state and mutability to the boundaries of your program, where it connects to its environment.


So you feel your program is better off for having only one connection pool and being unable to use another?

Regarding caching your processor tends to feel otherwise and has multiple levels of cache because globally mutating state in a coherent way is slow as molasses.

I can't wait to hear what awesome program needs a connection pool and access to the JIT at the same time. Are you writing a SQL compiler that sends mail?

Is your awesome program by any chance written in JavaScript or PHP?

PS. Databases aren't really global mutable state as in when you ask your database for a count of rows in a table the answer you get back won't ever change because magic.

Also, because of the ways database ARE global mutable state you run into all the issues you typically do with global mutable state. And then the places you do have global mutable state turn into performance nightmares. (Read: Hot spots)


Not really, even in my worse app I never wrote anything in the face of a user... And you can't treat a user as a global mutable state if your code doesn't directly mutate it. I obviously agree with the concept that he is a very good example for an external system on which you have no control.


I agree, but this doesn't give much guidance about when global state is okay.

The way I think about it is that if you create a global variable for a user, you are saying that there is always a one-to-one correspondence between a user (maybe the current user) and a process.

In some apps (such as command-line apps) you can assume that the OS user is always the current user. But if you ever write an app that might be used by more than one person, you probably want to keep track of which user you're currently talking about. For example, are there multiple profiles? User switching? Simulated users in tests? There are relatively few objects that we can sure will always be one-to-one with a process. When writing libraries this is especially uncertain since you don't know the app's architecture.


Not to mention the Internet. There will always be some external mutable state. At best you can view other nodes in a computer network as actor objects.


The thing is, in JavaScript, unless you are extremely rigid about how you program, it is very easy to cause bugs.

It is basically a language which trains you to try to err on the side of caution.

It's often preferable to be religious when you can be, as even if you do there will still be plenty of bugbears left to watch out for.


JavaScript is actually a better case than most languages as it doesn't support threads.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: