All I really wanted to do was learn a little bit more about different models for error handling, but then I kept seeing “effects” and “effect systems” mentioned, and after reading about Koka and Effekt I think I’ve been converted. I want effects now. So here’s what I wished I could have read a few weeks ago.
To start with, you need to remember that functions don’t exist. They’re made up. They’re a social construct.
Your CPU doesn’t know or care what functions are, they’re purely a book-keeping abstraction that makes it easier for you to reason about your code, and for the compiler to give you some useful feedback about your code. It’s the whole idea with structured programming: build some abstractions and have a compiler that can make guarantees about them.
I’ve never really done much assembly so this wasn’t something I’d had to contend with too much, but functions are interesting because they’re a fixed entry point with a dynamic return point. Let me show you what I mean with this C program:
int first_function () { // ... return 10 ; } int some_function () { // ... int number = first_function (); return 4 + number ; } void main () { first_function (); some_function (); }
When this program is compiled, the compiler knows exactly where the instruction pointer needs to jump to get to first_function and some_function , since it knows exactly where in the executable it put them. Chances are that if you looked at the assembly they would each just be a single instruction to jump a nice fixed offset.
What happens when we get to the return statements? first_function is called from both some_function and main —there isn’t just a single place that we can jump back to. The compiler doesn’t know when it’s generating the code for first_function who’s going to be calling it.
How this works is that alongside any function arguments, there’s an invisible argument passed that contains the position of the instruction where it made the jump to the top of the function. The compiler knows what the instruction address is—it’s the one that puts it there—and so for each function call site, that’s just a static piece of information that gets passed in. At the end of each function, the compiler just has to generate some code to read that argument (usually stored in a CPU register somewhere, but it doesn’t have to be), jump back to that location, and continue execution.
You don’t think about this complexity because the abstraction is so solid and yet gives immense flexibility to write complicated programs.
The resolution of which function to call can get more complicated by taking into account the number of arguments and their types, instead of just the name of the function.
... continue reading