Home > Uncategorized > Grasp, A .NET Analysis Engine – Part 4: Runtime

Grasp, A .NET Analysis Engine – Part 4: Runtime

February 28th, 2012 Leave a comment Go to comments

In parts 2 and 3 we laid out the initial elements which model the analysis of a data set: variables and calculations. In this post, we will start to define a context in which calculations can operate on variables.

Schema

As stated in part 2, we can describe the set of all variables known to a system as its schema. However, that isn’t totally accurate: just as a database schema has tables and logic, such as stored procedures, a Grasp schema is really the set of all variables and calculations that apply to them. We can model this with a collection of each:

public class GraspSchema
{
  public GraspSchema(
    IEnumerable<Variable> variables,
    IEnumerable<Calculation> calculations)
  {
    Contract.Requires(variables != null);
    Contract.Requires(calculations != null);

    Variables = variables.ToList().AsReadOnly();
    Calculations = calculations.ToList().AsReadOnly();
  }

  public ReadOnlyCollection<Variable> Variables { get; private set; }

  public ReadOnlyCollection<Calculation> Calculations { get; private set; }
}

An instance of this class describes the entire context in which the variables and calculations are relevant. This could be a survey, a math test, a quality checklist, or a college’s financial report. A schema is the container for a system’s design; it is the static portion of the engine.

Runtime

The dynamic portion of the engine is where we associate values with variables and actually apply calculations. This is known as the runtime. It is the context in which values live and change; we can consider a runtime as a particular instance of a system described by GraspSchema. For example, if a schema represents a math test, then a runtime would represent an instance of that test for a particular student.

The core concept within the runtime is the binding of variables to data. Binding is a highly overloaded term in the field of software, but in this case, it simply means giving a variable a value:

public class VariableBinding
{
  public VariableBinding(Variable variable, object value)
  {
    Contract.Requires(variable != null);

    Variable = variable;
    Value = value;
  }

  public Variable Variable { get; private set; }

  public object Value { get; set; }
}

We give the Value property a public setter so the runtime can change its value as it applies calculations. A runtime simply associates a schema with a set of these bindings:

public class GraspRuntime
{
  private readonly Dictionary<Variable, VariableBinding> _bindingsByVariable;

  public GraspRuntime(GraspSchema schema, IEnumerable<VariableBinding> bindings)
  {
    Contract.Requires(schema != null);
    Contract.Requires(bindings != null);

    Schema = schema;

    _bindingsByVariable = bindings.ToDictionary(binding => binding.Variable);
  }

  public GraspSchema Schema { get; private set; }
}

This establishes the base state of the runtime. We index the bindings by their associated variables so that we can perform efficient lookups when applying calculations. In order to find the binding associated with a variable, we use the TryGetBinding method:

private VariableBinding TryGetBinding(Variable variable)
{
  VariableBinding binding;

  _bindingsByVariable.TryGetValue(variable, out binding);

  return binding;
}

Here, we use the TryGetValue method of the dictionary to look up the binding. If it is not found, binding will be assigned null (this is in contrast to using the indexer, which would throw an exception if the key is not found.) We use the null sentinel value to create bindings for unbound variables:

public void SetVariableValue(Variable variable, object value)
{
  Contract.Requires(variable != null);

  var binding = TryGetBinding(variable);

  if(binding != null)
  {
    binding.Value = value;
  }
  else
  {
    binding = new VariableBinding(variable, value);

    _bindingsByVariable[variable] = binding;
  }
}

This is a simple update or on-demand initialization of a binding. This allows the runtime to grow along with calculations, as we may not have values for variables until they are calculated (such as Acme.Bookstore.OperatingProfit).

The other side of the coin from setting values is getting values:

public object GetVariableValue(Variable variable)
{
  Contract.Requires(variable != null);

  var binding = TryGetBinding(variable);

  if(binding == null)
  {
    throw new UnboundVariableException(variable);
  }

  return binding.Value;
}

Here, we also attempt to retrieve the binding associated with the variable. However, we throw an exception when the variable is not bound. This highlights algorithm errors which may reference a variable before its calculation or otherwise be expecting a variable which is not known to the runtime.

Calculators

We have discussed how to model variables within a runtime. Next, we move on to calculations. A calculation is the process of reading variables from the data set, performing some computation, and setting an output variable with the result. We can model this with an interface that represents an operation on a runtime instance:

public interface ICalculator
{
  void ApplyCalculation(GraspRuntime runtime);
}

This encapsulates all logic associated with reading and updating variables. With this abstraction in place, we can add an instance of it to GraspRuntime and expose a method which invokes it:

public ICalculator Calculator { get; private set; }

public void ApplyCalculations()
{
  Calculator.ApplyCalculation(this);
}

We also need to add it to the constructor of GraspRuntime (not shown). The ApplyCalculations method simply asks the calculator to apply its calculations to the runtime which owns it. Obviously we will need to apply multiple calculations to a particular runtime, so first we create a composite implementation of ICalculator to do so:

public sealed class CompositeCalculator : ICalculator
{
  public CompositeCalculator(IEnumerable<ICalculator> calculators)
  {
    Contract.Requires(calculators != null);

    Calculators = calculators.ToList().AsReadOnly();
  }

  public ReadOnlyCollection<ICalculator> Calculators { get; private set; }

  public void ApplyCalculation(GraspRuntime runtime)
  {
    foreach(var calculator in Calculators)
    {
      calculator.ApplyCalculation(runtime);
    }
  }
}

We simply apply a collection of ICalculator implementations as though they are a single implementation (the basic tenet of a composite object). Things get interesting when we define a calculation in terms of a function which accepts a GraspRuntime and returns the output value:

public sealed class FunctionCalculator : ICalculator
{
  public FunctionCalculator(
    Variable outputVariable,
    Func<GraspRuntime, object> function)
  {
    Contract.Requires(outputVariable != null);
    Contract.Requires(function != null);

    OutputVariable = outputVariable;
    Function = function;
  }

  public Variable OutputVariable { get; private set; }

  public Func<GraspRuntime, object> Function { get; private set; }

  public void ApplyCalculation(GraspRuntime runtime)
  {
    runtime.SetVariableValue(OutputVariable, Function(runtime));
  }
}

Here, we implement ApplyCalculation by applying a function to the specified runtime and assigning the result to the output variable. This is a very straightforward implementation which places the majority of the heavy lifting on the function. We create the function by compiling the Expression instance we used to model the Calculation class; if that does not sound familiar, don’t worry, we will see more in the next post.

Summary

We codified the representation of a system’s schema, the static portion of the engine. We also modeled the dynamic portion of the engine by associating variables with values in a context called a runtime. We then implemented the getting/setting of those values and created an abstraction for applying calculations.

Next time, we will start examining the compilation process, which generates a runtime from a schema.

Continue to Part 5: Executable

Tags: , ,