Flite Careers

Philosophy and the Flite Ad Runtime - Part I

Metadata Is Just Data

Computers are their own meta. Alan Turing, with inspiration from Kurt Gödel and his Incompleteness Theorem, remarkably before computers were even invented. Alonzo Church showed pretty much the same thing, at about the same time, so the result of their work is often called the Church-Turing theorem. The goals of these 20th-century intellectual giants were epistemological in nature, and the implications are profound. It seems surprising, then, when applied needs not only take advantage of these implications, but in so doing, also use some of the same techniques pioneered in these philosophical quests. This is the story of how, in a small way, Flite piggybacks on the shoulders of these giants.

Flite’s mission is to empower advertisers. To do this we need a metasystem - a system to interpret the metadata of display advertisements and set up the appropriate environment in which to run the ad. The result is called the Flite Ad Runtime (the “runtime”), and the implications, while far less profound than those of the work of Gödel, Church and Turing, are nonetheless good news for people with a need to create and distribute rich, interactive advertising. I inherited the task of maintaining and extending the runtime, and have been delighted to find that it contains many of the same techniques used by Gödel/Church/Turing (GCT) in their firmament-busting proofs.

There are a several aspects of GCT in the runtime that I would like to describe, but I will start with only one, in the hope that some other sentient beings are likewise afflicted with an obsession about this stuff, and demand for more will be at least measurable (measurable in the sense that we can measure when someone spends more than 2 minutes reading this post). I sum up today’s theme thusly: Metadata is just data.

Kurt Gödel, Time Magazine’s Mathematician of the Century, has been called a “metamathematician,” someone that works with mathematical systems. In his Weltanschauung-shattering Incompleteness Theorem, he actually shows that any really cool system of mathematics (a “formal system”) can be repurposed to become its own metasystem - that is, it can be used to talk about itself, to reveal truths about the system itself, not just truths within the system. This is quite remarkable, so I will repeat it: To study the system of mathematics, all we need is mathematics itself.

Alan Turing was inspired by Gödel in his attempt to address a related problem, namely David Hilbert’s Entscheidungsproblem (German for decision problem). Turing, like Church before him, dashed the hopes that Hilbert and many other mathematicians and philosophers had for an algorithm that would determine the truth or falseness of any mathematical statement. In so doing, however, he also showed that every computer could, in theory, emulate every other computer, including itself. Every computer is potentially its own metasystem, as well as every other computer’s metasystem. There are practical limitations to this, but for the runtime, all modern computers are up to the task, and the runtime becomes the metasystem under which ads created in the Flite Ad Studio run.

How does all this philosophy apply to a practical problem like running interactive advertising? I promised to describe some of the techniques shared by GCT and the runtime. One key innovation used by Gödel as he crushed the dreams of the mathematicians Alfred North Whitehead and Bertrand Russell, who had just published the second edition of their masterwork Principia Mathematica, was to turn the metadata of a formal system into mathematical data, so that a formal, mathematical system could work with it. He then used those symbols to create a mathematical proof that said something about not just the equations it used, but also about formal systems themselves. At Flite we do not do anything quite so dramatic, but we do need to abstract the metadata of an ad into data so the runtime can work with it.

The simplest example of this involves some of the metadata for a single component of an ad. We call these data “configuration parameters.” These parameters customize the base components that make up the ad, specifying information like colors, URLs and positions. Each component of an ad has its own set of configuration parameters, which can change, and new components can appear at any time, so it would be neither practical nor scalable to hard-code these parameters into the runtime. The runtime does not know what the parameters are, but it knows that they are there. The solution is a simple parameter map, built from the JSON data created by the ad studio where the ad got assembled:

1
2
3
4
5
6
7
8
9
10
11
private var _configMap:Object;
private function mapConfigParams(configParams:Object):Object {
  if (configParams != null) {
      _configMap= new Object();
      for (var key:String in configParams) {
          _configMap[key] = configParams[key].value;
      }
      return map;
  }
  return null;
}

The components themselves know what configuration parameters to expect, and they can ask the runtime for them by parameter name. The runtime looks for a matching property in the configuration parameter map and returns it or an appropriate default value if not found. Convenience getters make sure the value returned is of the type required by the component:

1
2
3
4
5
6
public function getNumber(key:String):Number {
    if (_configMap[key]) {
        return Number(_configMap[key]);
    }
    return NaN;       // Not a Number
}

Within the component, using the Flite API:

1
var howMany:Number = api.configuration.getNumber(“howMany”);

Notice the component has to know to ask for a parameter by name but the runtime does not need to know that name to supply it. The ad’s metadata has become, to the runtime, plain ol’ data. Anytime parameters with unknown metadata - in this case the parameter name - need to be manipulated, a process like this can be useful.

Now to a deeper example. Components can expose methods (“actions”) that can be called by other components. These action can accept arguments. So for a given action, the runtime needs to be able to map a trigger from another component to the target component’s method (the action) and pass it the arguments. None of the metadata for these activities can be known to the runtime beforehand, so it all must be abstracted - it must become data that can be stored and manipulated.

Start with a data class that holds all information needed to set up an action. Note that some of this data, like the type, will be hard-coded into the target component as metadata.

1
2
3
4
5
6
7
8
9
10
public class Action {
  public var type:String;         // What the action does, e.g., navigate to another page
  public var options:String;      // Options appropriate to the action’s type, e.g., in which window should a navigation go?
  public var payload:String;      // arguments to be passed to the function, e.g., the URL of the navigation
  public function Action(action:Object) {
      type = action.type;
      trigger = action.trigger;
      payload = action.payload;
  }
}

The metadata for this ad will be saved in JSON format by the ad studio, and passed to the runtime when it is time to run the ad. This gets deserialized into an array of action objects. For each type of action, an array is created to hold all of the actions of that type, then a listener for that type is added:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private var _actions:Object;
function setUpActions(actions:Array):void {
  _actions = new Object();
  var hasTrigger:Object = new Object();
      
  // For each type of trigger (mouse events, custom component actions, etc), create an array with an entry for each trigger of that type.
  for (var i:uint = 0, l:uint = actions.length; i < l; i++) {
      var action:Action = new Action(actions[i]);
      var trigger:String  = action.trigger;
      if (!_actions[trigger]) {
              _actions[trigger] = new Array();
      }
      _actions[trigger].push(action);
  }
  
  // For each type of action, add a listener that will parse the array of actions created above. 
  if (actions.length > 0) {
        if (hasTrigger[MouseEvent.CLICK]) addEventListener(MouseEvent.CLICK, mouse_CLICK);
        if (hasTrigger[MouseEvent.MOUSE_OVER]) addEventListener(MouseEvent.MOUSE_OVER, mouse_OVER);
        if (hasTrigger[MouseEvent.MOUSE_OUT]) addEventListener(MouseEvent.MOUSE_OUT, mouse_OUT);
      // custom “component” type actions get special treatment
        if (hasTrigger[“component”]) {
          for (var i:uint = 0, l:uint = _actions[“component”].length; i < l; i++) {
              var type:String = _actions[“component”][i].type;
              if (!_componentListeners[type]) {
                  _componentListeners[type] = new Array();
              }
              var container:* = getContainerById(payload);
              container.addEventListener(type, componentEventHandler);
          }
      }
  }
}

A single function handles all of the custom events. When a custom event (the action’s “trigger”) gets dispatched by another part of the ad, this function will run each action of the type specified by the trigger.

The event listeners get the array of actions for their type of event and execute the method specified for each. The handler for processing mouse click events looks like this:

1
2
3
4
5
6
protected function mouse_CLICK(evt:MouseEvent):void {
  var mouseActions:Array = _actions[MouseEvent.CLICK];
  if (mouseActions && mouseActions.length > 0) {
      mouseActions.forEach(runAction);
  }
}

For each action being processed, parse the options and call its method.

1
2
3
4
5
6
protected function runAction(action:Action, id:uint, list:Array):void {
  var type:String = action.type();
  var payload:String = action.payload;
  var options:Object = action.options;
  this["do" + type].apply(this, [payload, options]);
}

Finally do it. Certain actions, like navigating to another page (type = “link”), are programmed into the runtime.

1
2
3
4
protected function dolink(payload:String, options:Object = null):void{
  options ||= new Object();
  navigateToURL(new URLRequest(payload), options.window);
}

A special type of action (“custom”) indicates custom actions that are unique to a specific component. The method and container identifier must be added to the options by the ad studio when the action gets configured by the ad creator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected function componentEventHandler(evt:Event):void {
  var componentActions:Array = _componentListeners[evt.type];
  if (componentActions && componentActions.length > 0) {
      componentActions.forEach(runAction);
  }
}

protected function docustom(payload:String, options:Object = null):void {
  options ||= new Object();
  var container:* = getContainerById(payload);
  if (container) {
      container.callMethod(options.fn, options.args);
  }
}

Notice how much easier it is to handle actions whose type is known beforehand (e.g. “link”) than it is to handle actions of unknown type. This is because the link type actions can be handled using hard-coded metadata within the runtime (function dolink(), the name of the function being the metadata). The custom actions require another layer of abstraction, thus adding complexity and risk. In all cases, however, the runtime eventually requires its own metadata to handle the action.

The theoretical foundations of computation were inspired by, and created to answer, some of the leading philosophical questions of the day. So it is appropriate that from their beginnings, computers have profoundly influenced many thinkers. Computation has replaced mechanics as the leading paradigm in our modeling of the mind, and many physicists also use it as a model for the universe. If the universe is a giant computer, if our brains really do function by processing information, and if our consciousness is to be found somewhere therein, then the discoveries of Kurt Gödel, Alonzo Church and Alan Turing could define fundamental limits to our own potential and the possibilities of the world we inhabit. However, if the physicist David Deutsch is correct, these discoveries might simply be stepping stones to an existence which, while constrained by the laws of physics and rules of logic, nonetheless offers infinite room for growth. As developers, we continue to use the concepts discovered by these pioneers, and it remains our job to explore their limitations, and create new ways to overcome them.