Home > Uncategorized > Grasp, A .NET Analysis Engine – Part 5: Executable

Grasp, A .NET Analysis Engine – Part 5: Executable

In part 4, we started outlining the execution of the Grasp engine by defining elements for representing a system’s schema and runtime. In this post, we take another step toward generating runtimes from a schema.

Between Schema and Runtime

A schema represents the raw ingredients for a runtime: the variables and the calculations which apply to them. However, the calculations are in the form of expression trees, which are just data structures; we cannot use them to actually carry out the logic they represent. We need some notion of a compiler to turn the expression trees into something executable.

In part 4, we defined the ICalculator interface, which exposes the ability to operate on a GraspRuntime to perform a calculation. Implementations of this interface, specifically FunctionCalculator, would be the output of our hypothetical compiler. By associating an instance of ICalculator with the schema from which it originated, we get the executable form of a system:

public class GraspExecutable
{
  public GraspExecutable(GraspSchema schema, ICalculator calculator)
  {
    Contract.Requires(schema != null);
    Contract.Requires(calculator != null);

    Schema = schema;
    Calculator = calculator;
  }

  public GraspSchema Schema { get; private set; }

  public ICalculator Calculator { get; private set; }
}

This represents the potential to run calculations, but without any specific data. This is much like a program executable, which defines the potential to run the program but is not an instance of that program.

Now that we’ve defined executables, we can add the ability to compile to them right on the GraspSchema class we defined in part 2:

public class GraspSchema
{
  // …

  public GraspExecutable Compile()
  {
    return new GraspCompiler(this).Compile();
  }
}

We will explore the GraspCompiler class later; the key takeaway here is that the compilation process takes a schema as input and produces an executable as output. If we replace "schema" with "source files", we would be describing the traditional definition of a compiler. Modeling Grasp after this well-known process lets us leverage existing concepts and language to facilitate understanding.

Generating Runtimes

The defining attribute of an executable is the ability to create instances of itself. Each of these instances is called a runtime (as defined in part 4). What differentiates one runtime from another is the data that lives within; for example, two students taking the same test will have different sets of answers, thus requiring each to have a separate runtime.

This implies that, in order to generate a runtime, we must seed it with its own specific data. This might be persistent data if a user saved a test or survey to come back later; it could also simply be the default values for each variable. In any case, we need a mechanism that encapsulates the mapping of an executable’s variables to their initial values:

public interface IRuntimeSnapshot
{
  object GetValue(Variable variable);
}

This straightforward interface represents the state of a runtime at a given point; we can use it to initialize the variable bindings of a new runtime. To do this, we add the GetRuntime method to the GraspExecutable class:

public GraspRuntime GetRuntime(IRuntimeSnapshot initialState)
{
  Contract.Requires(initialState != null);

  return new GraspRuntime(Schema, Calculator, GetBindings(initialState));
}

private IEnumerable<VariableBinding> GetBindings(IRuntimeSnapshot initialState)
{
  return Schema.Variables.Select(
    variable => new VariableBinding(variable, initialState.GetValue(variable)));
}

This is how we create instances of executables for a specific data set. The entire workflow for creating a runtime and apply calculations, then, looks like:

var schema = new GraspSchema(…variables and calculations…);

var executable = schema.Compile();

var runtime = executable.GetRuntime(…initial state…);

runtime.ApplyCalculations();

This is Grasp’s external API. In a typical application, we would compile the schema into an executable once, then use it to generate many runtimes. Following the student/test example, an application may take a test defined in XML, build the schema, compile it, and store it at the application level. Then, we would get a new runtime for each student which takes the test, sandboxing their data, but only pay the performance tax for compiling the schema a single time.

Summary

We identified the need for an executable form of a schema and added the ability to create instances of it called runtimes. We then created an abstraction that maps variables to their initial values and saw the process of generating runtimes from a schema.

Next time, we will look at GraspCompiler and see how it turns Calculation objects into executable code.

Continue to Part 6: Validating Calculations

Tags: , ,