Hacker News new | ask | show | jobs
by showell 6012 days ago
This seems like a wild exaggeration:

"The behaviour of every function in a mutable, imperative environment is dependent upon the state of all of the other (variables|attributes|bindings|whatever) in your program at the time the function is invoked."

You can write a function in an otherwise mutable and imperative environment like Python:

pi = 3.15 # damn, typo for the value of pi! pi = pi * 15 # mutate it, wrongly again

def sum(a, b): return a + b print sum(7, 8)

Please tell me how sum() depends on pi. The program correctly prints 15.

6 comments

It doesn't. It is a wild exaggeration. It usually isn't the 300+ variables, just a few obscured by multiple levels of indirection that can cause unexpected inconsistencies. (It gets even worse when you drag in inheritance...)

Each function only depends on the arguments passed in, and variables outside of its scope which are referenced. However, the same is true of every function called inside the function, and the dependency is transitive, so the impact of changes to variables that aren't explicitly threaded through leaks outside to functions that never mention them. That's the problem. Hidden state.

It's also the kind of issue that usually looks incredibly contrived in small (say blog-post-sized) code samples, but isn't funny anymore when you have to deal with big lumps of spaghetti code. Global variables make reasoning about dataflow hard. Real problem, poor description.

Clearly you can see in the implementation of the function that it doesn't rely on any globals. However if you look at the function as a black box, you cant really be sure what global state it relies on. This could be an issue if you use larger libraries you didn't write yourself (or in my case, if I use code I wrote more than two weeks ago!).

In a language like Haskell the type signatures clearly indicates which global state the function (and other functions called by it) has access to, which makes it a lot easier to reason about side effects, even for code where you haven't read the source.

Of course, this approach also have its downsides. If you decide for debugging purposes to add a logging function to an otherwise pure function deep inside a pure part of your program, you may have to change a whole lot of code.

"Sum" is written functionally, thus it is not dependent on the outside world. You used a functional stateless method to show that imperative stateful programming is safe.
How do you know that "+" doesn't depend on pi? Oh. Now you see the problem.
If you need 100% guarantees, then yes, do things in a purely functional way. However, I think the point is that in any sane program you can easily and safely make the assumption that + does not depend on pi in this situation.

Yes, anything _can_ depend on any and all globals, but with proper practices, documentation, code reviews and what have you, one can make simplifying assumptions on what _will_ happen.

"...but with proper practices, documentation, code reviews and what have you, one can make simplifying assumptions on what _will_ happen."

Or you can leave out the practices, documentation, and code reviews and use a functional language and get the same result. Yes, those things are useful for other reasons, but the current argument is about avoiding the inherent dangers of global variables.

Of course, the point is that a caller of any function in an imperative environment doesn't know what state its implementation depends upon.
It is an exaggeration; but only a wild one if you are doing very simple examples.

Imagine you have a 'mutable' program and I have a line that says something like this.

(defun frob (a b) (+ (foo a) (bar b)))

Not only is frob dependent on the definitions of foo and bar, it is also dependent on the the definitions of any mutable globals that are within foo and bar.

If you imagine your program as a directed cyclic graph of functions, and pretend that mutable globals are really functions that set or get a position in memory, you can see that adding more globals to your program (and using them) increases the complexity of your graph mostly by adding cycles to it. (As well horizontal jumps across the entire graph).

It is true that sometimes you need these cycles to write a program (vs. a program that causes your computer to heat up and do nothing), but it is not wildly inaccurate to say that using them all the time makes your program more complex.

I'm not even sure that I'm on board with the supposition that global variables shouldn't be in the language; (saying 'shouldn't' exist at all to a language feature is entirely non-pragmatic).

I think it is more accurate to say that they should be in the language, but they should have a cost greater than locals, and it should be best practice to use them as little as is reasonably possible.

Also frob is dependent on the (mutable) definitions of foo and bar. Functions can be redefined in e.g. Python and Scheme. (And functions are variables, too.)
Same for +.
I see the main point, and I try not to use globals when I can, but I can't help but recall a quote I read from somewhere: "People who are afraid of globals are usually afraid of girls, spiders, etc."
I wouldn't trust the person who told you that, anymore.

Pretty much the last decade of software engineering has been a slow realization how how untenable the use of large amounts of global state is.

You're right, of course, but sometimes globals can be the magic sauce you need to hack out something quickly. (I find global hate to be similar to 'goto' hate.)
Ah, the "I'm a big man, I code by writing bits to the platters with a magnet" argument. (Quick, prize to the first xkcd link here!)
I found it an amusing quote, and I'm a strong believer in using the right tool for the right task. :)