It's been known since the time of Turing that what's computable in one reasonably powerful programming language is computable in every other one. As a result, we can say that large classes of programming languages are 'equivalent' in the sense that they can emulate each other. However, not all programming languages have natural equivalents of all the others' features.
This is the idea behind Greenspun's Tenth Rule:
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
The usual corollary is "including Common Lisp". We could say the same for C: most C compilers are implemented in C and compile themselves. Similarly, the Glasgow Haskell Compiler is written (mostly) in Haskell. Actually, given that C is the implementation language for so many interpreters, it's easy to see that one could, in principle, write a program in language X by directly creating the abstract syntax tree and passing it to an "evaluate" function, all in C.
Changing tack slightly, let's take a brief look into the insides of Milliways III. (I hear some of you saying "Eugh!" :) Currently each user has their own process, and each process has a named pipe. To send a message to a user, another process (which must be owned by the same user, hence the executable has to be setuid) writes to this pipe with the message in a particular format. Currently there is no locking, but all writes are of sizes below the threshold which guarantees that the write is atomic, so there are no race conditions. Relying on such a specification is rather ugly, plus there are many different places in the source which perform this writing. It would also be interesting to make Milliways III a Marvin client, making it possible for everyone to use Marvin without forsaking the features of mw3.
To this end, I created the newipc branch, and set about designing a higher-level message-sending function. Most of the places that write to the pipe do so in exactly the same way; there is a lot of boilerplate code in there. So it is possible to replace these with one function that decides what to do according to the data sent to it by the calling function.
Here is the prototype of the main abstraction in ipc.h:
typedef char * (*msgtest)(struct person *, void *);
void mesg_filter(msgtest test, void *testobj, enum mesgtype type);
The first line defines the function pointer type used in the second. In this way, mesg_filter is what's known in the functional programming world as a higher-order function: it takes a function as an argument. Recall the signature of qsort in the standard C library:
void qsort(void *base, size_t nmemb, size_t size,
int(*compar)(const void *, const void *));
qsort takes a comparison function (compar) as an argument, to decide whether one element of the array is less than, equal to or greater than another. Similarly, when some part of Milliways wants to send a message to another user, it calls mesg_filter, passing in some arbitrary data along with a function which is given a struct person to decide whether or not to send a message to this person, and if so, what message. If it shouldn't send anything, it returns NULL. The arbitrary data typically includes a pointer to the message, so the function can vary its behaviour depending on the type of message being sent.
In C, functions are not first-class objects. Basically what this means is that you can't create functions on the fly - they have to be declared beforehand. "How do you create a function on fly?" I hear you ask. Basically you do this by creating a closure. Here is a trivial example in Python:
def mk_adder(n):
def addn(m):
return n + m
return addn
Suppose we do fn = mk_adder(3). Then fn is a function which takes a number and returns the sum of it and 3. fn is a closure. What happens here is that the Python interpreter notices that addn (the inner function) uses the local variable n, and so it makes note of it before returning the created function. Actually, what it returns is not an ordinary function, but an object that can be called like a function, with some extra information specifying the value of n. This is a closure: basically a struct containing a) a function, and b) some data that the function uses.
In using mesg_filter, the function test that gets passed in also needs some extra information to do its job. Maybe we want to send a normal message, or maybe we want an emote. Additionally, people using mud-mode get different messages to those who aren't. The testobj pointer is a pointer to some arbitrary data structure containing this information. Together the function test and the black box data object testobj (note its type, void *, which means mesg_filter knows nothing about it except where it is in memory) constitute a kind of closure. Because C doesn't have the syntactic sugar to do this automatically, we have to do it manually instead, but in the process we've emulated part of a functional programming language in C.
You might be thinking that the concept of a function associated with some data sounds familiar. Consider the following C++ snippet:
class Msgtest {
public:
virtual char * test(struct person * p) = 0;
}
Looks like just a function type with an abstract class wrapped around it, right? Well yes, but we can inherit from it and add our own data. (Equivalently, in Java we could define an interface with just this method and implement it.) With this abstract class, our "higher-order" message passing function gets this signature:
void mesg_filter(Msgtest test, enum mesgtype type);
All we've done is replace the void * pointer with an abstract class. The idea is that a calling function will define some class which inherits from Msgtest, and adds its own data fields. This is very similar to the idea of a closure. If we were really using closures, we wouldn't need to tell mesg_filter anything about extra data; we don't need the additional scaffolding of an abstract class definition or a pointer to a black box. Here's what the signature of mesg_filter would be in Haskell:
mesg_filter :: (Person -> Maybe String) -> Msgtype -> IO ()
The parenthesised part of the signature indicates that the function takes a function as an argument. All we need to note in the type of mesg_filter is that this function takes a Person, which is some data object containing information about the person we might be sending a message to, and possibly returns a string - if it doesn't, we don't send a message. No mention of the extra data, and no need even to acknowledge it, like we did in C using a void pointer or in C++ using an abstract class. (The Maybe even gives more information than the C and C++ signatures - it's obvious now that it might not return anything.) How, then, do we pass in the extra data we need to make decisions? Answer: closures.
sayto :: Person -> String -> IO ()
sayto person mesg = mesg_filter filter Text
where filter :: Person -> Maybe String
filter p = if p == person
then Just mesg
else Nothing
Similarly to the Python example, filter (a locally declared function) makes use of the local variables person and mesg. The function that gets passed to mesg_filter isn't an ordinary function: it's associated with these two extra data. From the point of mesg_filter, though, there's no difference.