Why do we need function values in MiniMe?

When programmers use the word 'function', they might mean one of three different things (depending on how they use the word). It is important to understand and distinguish between these three different uses of the term 'function'.

Function Body

What is probably most often thought of as a function is, what I'll call, a function body. This is the block of executable code that will be run when the function is called.

The reason that function bodies come to mind first is that our job as programmers is to write function bodies.

But from MiniMe's point of view, this view of 'function' is the least interesting. We need to expand our view of function!

Function Activation

The next form of 'function' to think about is the notion of a specific function activation. If I call the same function two different times, I'm using the same function body; but each call receives different parameters and has its own set of local variables. The parameter values and local variable values associated with one specific call to a function makes up a function activation record (FAR). When I call the same function a second time I get a second FAR: two calls -> two FARs -> one function body.

This is important to understand with the ability to nest functions (define one function body inside of another function body). (Why nest functions?). The issue is that the nested function may not only refer to its own parameters and local variables, but may also refer to the outer function's parameters and local variables.

So when the nested function gets called, it needs access to two FARs: the one for itself, and the one for the outer function. If the nesting level went three deep, then there would be three FARs accessable.

This is done by adding a pointer to the outer FAR within the nested function's FAR. In effect, the outer FAR pointer is like an additional hidden parameter to the nested function.

So when calling a nested function the outer FAR must somehow get passed as an additional hidden parameter. If the nested function is called directly from within the outer function, the compiler knows that it is a nested function and can handle it specially. But what if I pass the nested function as a parameter to some other function that was compiled having no idea what kind of function it might get?

Function Value

When we consider being able to pass functions as parameters we realize that when the compiler compiles a call to function parameter, it doesn't know what kind of function may be passed. The goal is to be able to pass any kind of function and have it work properly.

This is where a function value comes in. Function values are what are passed at run time as function parameters and are what actually get called at run time.

When, for example, I want to pass a nested function as a parameter, the function value that gets passed needs to have not only a pointer to the function body to execute, but also a pointer to the outer function's FAR (done with a curried function). But when I want to pass a global function, the function value only needs a pointer to the function body (done with a standard function).

It is important to note that we don't call function bodies, but rather, we call function values. In MiniMe, a function value is always needed (at least internally) in order to do a function call.

Different Kinds of Function Values

MiniMe has three different fundamental notions of functions: standard functions, member functions, and coroutines. It also has two higher-level function concepts: composed functions and curried functions. See MiniMe's Idea of Functions for an explanation of each of these.

Function values are a built-in data type in MiniMe. The programmer can not define additional function values because they are tightly integrated into way the language works. There is a different kind of function value for each of the five kinds of functions:

The bytecode interpretor uses these objects to actually do the function calls at run time. Each of these objects sets up the calls and accepts parameters in different ways in order to turn the function call request into its view of what a function call is.

Standard Function

Standard function values only have a pointer to a function body. They create a new FAR to do the call and transform object send semantics into standard function semantics by just passing the 'self' argument as the first argument to the function.

Member Function

These only have the name of the function to invoke. They only respond to the 'send' type calls by looking the function up in the class of 'self' and then calling that function.

Coroutine Function

These only represent started coroutines. They are different from the other function values in that they are not immutable. This is because they remember the return information to know where to branch back to in the coroutine. This return information changes each time the coroutine does another call (which means it's then waiting at a different point in the code for a return value).

Coroutine functions also do not create a new FAR each time they are asked to do a function call. Rather, they use the arguments passed as return data and merely resume the already created FAR for the coroutine.

Composed Function

These only have two pointers to other function values. It sets up a nested call to these functions.

Curried Function

These have a pointer to function value, a vector of argument values and the argument offset to insert these values at within the argument list.

SourceForge.net Logo