Side Effects in a C-Like Language

Posted by Rusky on March 9, 2011, 5:05 p.m.

tl;dr: What if there were no global variables or functions or anything else and functions could only access a limited collection of memory regions, hardware, etc.? Programs would be more structured and composable, and potentially more secure.

Most mainstream languages just let you change global state willy-nilly. Change global variables, mess with the file system, draw to the screen, etc. from wherever you are. A lot of the time this isn't a problem, because people have "best practices" to follow that help them avoid unexpected interaction between different parts of their programs. However, I would like these practices to be enforced or at least encouraged by the language itself.

Functional languages discourage or even ban any state at all. Once you get used to doing everything as arguments and return values, this is actually a pretty nice way to program a lot of the time. However, despite the optimization opportunities this offers, it is sometimes hard to write efficient programs. That and the raison d'etre of low-level languages, control over all aspects of your program, make the complete elimination of state not an option.

So, let's look at how these languages deal with stateful environments, like just about every operating system on the planet. Haskell has something called the IO monad to encapsulate input/output with the rest of the world. It's a rather ugly solution for Haskell, but would it work for a C-like language?

IO actions look a lot like functions in C, and the Haskell runtime executes them for you starting with "main," allowing you to write a simple imperative interface between the environment and your purely functional code. Writing everything in IO is a bad idea, because you lose all the advantages of being stateless.

This concept in general also makes interactions with the OS, etc. second-class citizens. That's probably a bad idea for a C-like language, where you may be writing device drivers, games or other performance-sensitive code where the whole point is using an inherently stateful processor efficiently.

Functional reactive programming turns programs into (and this is a simplification) math-style functions from time to value. That would be a very good way to do functional programs, but it still may not interact well with traditional processors in high-performance and/or low-abstraction situations. I probably would like to write games that way, though.

Languages like Clean and Disciple use various trickery to encode side effects into the type of a function. Again simplifying a lot, you basically tell the compiler which functions can affect what state. This is maybe a little bit closer to what we want. However, when writing things like interrupt handlers where every cycle counts, you basically want to write machine code with nice abstractions that don't get in the way.

So, what if every function were allowed to modify state, but only within a specified scope? This is the same concept that most mainstream languages use, but taken a lot farther. First, throw out global variables, functions and anything else that could be used to accidentally affect another part of your program. Now, how do you do anything useful?

When the operating system starts, it has all memory and hardware in its scope. It can then pass only what each program needs as arguments to main. Then, main can pass only what each part of the program needs and so on down. This makes it explicit when you give the same memory or API or hardware access to more than one part of your program.

Hmm, I just described capability-based security. Either what I was trying to say fell out of my head or it really is the same thing, but hey. What do you think? Instead of just fopen() you ask for a file handle from your caller. It forces programs to be more modular and composable and even more flexible.

Comments

marbs 13 years, 9 months ago

It's a shame that an interesting blog like this gets no comments. Unfortunately, I can't think of much to say other than I am currently learning Haskell, and it's a very interesting language. It's the first time I've used a functional language, and it was quite hard at first getting my head around it. The whole idea of not using variables seemed near impossible, but makes sense the more I use it. From a developer's point of view, I am not sure what I would ever use Haskell for, but then again I have not been using it that long.

My main enjoyment in Haskell is that it's a new angle on things, which is refreshing and challenging. It reminds me of back when I first started learning GML, and every new concept I discovered was fun and exciting, opening up a world of possibilities. I'll never forget the sheer joy I experienced when I first discovered how to define my own variables in GML.

Rusky 13 years, 9 months ago

I've really enjoyed learning Haskell as well. At the moment, I'm using it to write a parser using the standard Parsec library. Haskell makes this a lot easier- ASTs are very simple and short to implement, parsing looks almost like a grammar for a parser generator and the Parsec library has all kinds of useful things like lexer and expression parser utilities.

I'd suggest reading Real World Haskell and Write Yourself a Scheme for some great applications of Haskell to useful, real-world problems. Along the way you'll get to learn a lot more cool concepts than (in my opinion) in most mainstream languages. Each and every one of them lets you write more concise and powerful code- it's like a box of candy that never runs out. :P