2014/03/20

Event-Reflexivity in Static Languages

<< First | < Prev

In the previous article, we looked at the idea of explicit registration of handlers for events in an event-reflexive system, and touched on the idea that it may be more useful (or in fact, required) when dealing with a static language like C, rather than a dynamic langauge like Perl. Today's story will look at the different requirements for static languages in more detail.

In the examples in previous posts we have been able to use dynamic language features to easily implemented named actions by simply creating functions of the right name, and dispatch to them simply by passing that string name to the central dispatch functions.

# In a handler module
sub add_user
{ ... }

# In dispatching code
run_events "add_user", @args;

This became especially useful when creating dynamic action names in the IRC cases

# In a handler module
sub on_message_JOIN
{ ... }

sub on_message
{ ... }

# In dispatching code
run_events [ "on_message", "JOIN" ], @args;

To be clear here, we have used the following abilities of Perl (though similar should apply to most dynamic languages) in order to easily implement this system:

  • The ability to invoke a function in a module based on a dynamic string at runtime
  • The ability to pass a variable list of arguments to a function as a simple list, without the intermediate dispatcher function having to understand them
  • The ability to return any kind of value or list of values from a function

In a static language, this simply isn't going to work. We'll need something much stronger to bind all these pieces together. We'll need a way to more strongly identify the named actions as hook points, some way to pass the arguments around between them, and some way to interpret the return values for possible methods of combination or short-circuit return

The first idea to handle this is simply to number the events in some sort of globally-defined enumeration. But this of course creates a single global numbering, and half of the point of event-reflexivity in the first place was to avoid this kind of centralisation - a central numbering would mean that plugin modules couldn't themselves create and emit new events. They can't just invent new numbers because they might collide with existing ones.

Perhaps what is required here is that the event-reflexivity core can allocate contiguous blocks of numbered events, and allow some kind of association between event numbers and friendly string names for the convenience of programmers and users. When a new module wishes to allocate some events, it can request a block from the core, and be given its starting number. While that number would be dynamically allocated between different instances of the system, or even different runs on the same machine, it would at least be constant throughout one program run, allowing other modules to bind or invoke them. The friendly naming system would then exist to allow programs to look up the current number for a known event name, to bind or invoke it.

typedef ERcore_event_id int;

ERcore_event_id ercore_allocate_events(int n_events,
                                       const char *evname[]);
ERcore_event_id ercore_lookup_event(const char *name);
const char *ercore_event_name(ERcore_event_id id);

Having a way to create the identity of named events, we next need a way to register a function to actually handle them. This is where our second problem arises - the problem of how to pass event arguments. Perhaps the simplest is simply to allow a single void * pointer argument, on the basis that the named event would document somewhere what this was supposed to point at - likely a structure of some kind. Because C lacks the ability to create true closures, it may be necessary to pass a second pointer argument at binding time, and passing both that and the event argument to the invoked functions at dispatch time.

void ercore_bind_event(ERcore_event_id id,
                            void (*fn)(ERcore_event_id id,
                                       void *args,
                                       void *data),
                            void *data);
void ercore_run_event(ERcore_event_id,
                      void *args);

Here we've only defined the simplest of the invocation functions, run_events, because any of the others would require some consideration of the return value as well. Here we start to run into problems of needing to know how to interpret the meanings of these values. This means we can't implement any of the more interesting invocation methods as seen in the second post (Kinds of Invocation in Event-Reflexive Programming).

Our handling of arguments isn't very satisfactory, forcing invoking code to always pack their arguments into a structure, and all the handling code to unpack them from it again. We've also made our arguments totally opaque to the actual dispatch system, meaning we can't do any of the interesting tricks we could do in dynamic languages where these are visible (such as those seen in the third post, Instrumentation and Logging in Event-Reflexive Programming).

Finally, by making the event identity a simple ID number and having opaque argument structures that the event dispatch core cannot inspect, we have lost our ability to perform dynamic dispatch based on some of the arguments, as seen in the fourth post (Hierarchies of Actions in Event-Reflexive Programming).

We have seen in previous posts that all of these abilities are useful things to have, in combination they define the essential nature of what Event-Reflexivity is really all about. It would be nice if we could have these things in static languages as well as dynamic ones.

This then leads to the final, and most expansive question of this series of posts:

How do you create a powerful system of event-reflexivity in a static language such as C? How do you cope with combining return values and short-circuit evaluation in those dispatch modes that require them? How do you pass different kinds of arguments to invoked functions in a useful and simple way? And how do you perform dynamic multiple dispatch on of pieces of the event identity?

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello,

    i read some of your post before, but i don't really get what your definition of "event-reflexivity" is. For me it looks like normal event-based programming. In your first post on this topic you asked about ordering and if the order should matter. You sure can write a system where the order matters, but typically in event-based programming the order should not matter, it makes it a lot easier.

    Every event should stay on its own. And every event should be able to notify all his subscribers in an unspecific order without breaking anything. If you have an event like Removing a user and and it deletes the home directory, just add events that get fired pre/post deleting of the folder. If your order is not important it also gives you the benefit that you can theoretically call every Event Handler in an own thread.

    To the question how event-based programming works in a static typed language. Well, it depends on the language. In C# something like this is really easy. In C# you have delegates that are at some point a container/anonymous function. You can just use a delegate for events, but it is better to use the "event" keyword in C#. It just uses a delegate and has some benefits. So in C# defining an event takes usally two steps.

    You define a delegate. In the delegate you define which return type and arguments your anonymous function needs to have. And then you use that delegate to create an event. For example you want an "OnNewUser" event that returns void and takes the "User" Object as an parameter you write something like this.

    delegate void UserDelegate(User user);

    After that you define your events that uses that definition

    event UserDelegate OnNewUser
    event UserDelegate OnRemoveUser

    You can reuse the same delegate as often as you wish. C# also has some short cuts in defining Delegates. There exists two generic Types to define a Delegate. The "Action" Generic creates a delegate with return type void. The "Func" Generic return a Delegate with a return Type. Another way to define the "UserDelegate" would be

    Action<User>

    That creates a Delegate woth void, and the first Parameter needs to be from Type "User". You can have up to 16 Paramters.

    Action<int, string, float>

    The "Func" Generic does the same, but the last parameter will be the Return Type.

    Func<string, int>

    For example that creates a delegate that takes 1 Paremter of type string and returns an int. So you also can write the events in such a way without first defining an delegate.

    event Action<User> OnNewUser
    event Action<User> OnRemoveUser

    Everyone can register on that event. But just functions with the same signature. And you sure can also use anonymous functions / lambda expression to register on that event. Lets say you have an "users" object with the two events you can now write something like this

    users.OnNewUser += user => Console.WriteLine("Added: " + user.ToString());
    users.OnRemoveUser += HandleOnRemoveUser;

    "HandleOnRemoveUser" is a function in the same scope. Writing a function without "()" at the end does not execute the function it returns a reference/delegate on that function. The same way how it works for example in JavaScript. Or in Perl you have to Write "\&FunctionName".

    An full example with events in C# looks something like this: http://pastebin.com/LNEPUVT1

    If you execute it the code will print:

    Added: [User: name=Sid Burn]
    Added: [User: name=Anonymous]
    Removed: [User: name=Sid Burn]

    So using event-based programming in C# is really easy. I also use this a lot.

    ReplyDelete