2013-11-29
My Little Machines Kickoff

Goal: To build a little HTML5 playground to experiment with my attempt at implementing Combinator Birds (see To Dissect a Mockingbird) and other toys. As a first step, we’ll break the process of computation (and eventually, logical inference) down into a simple in-page engine where there is an initial state (obtained via Reset), and a subsequent set of states (obtained via Step).

I’ll eventually add Resume, Run, Pause and Halt commands, but we’ll develop that incrementally. In a separate article, we’ll see how these commands are derived from the core concepts of an Initial Object obtained via Reset and the Monoid operation of Step, which concatenates a successor state to an existing computation history.

I’m referring to this initiative and the resulting tools as My Little Machines™.

Experiment A - Reset and Step with no Javascript

My first approach will be to build a little demo box that supports a Reset button and Step button. What goes on in the box will be a simple incrementing of computational steps. This demonstration has no termination condition, except perhaps when the integer in machineATime is wrapped via overflow to a negative number.

We’ll start out with pure AngularJS and no actual Javascript code, and see how far we get. Just to be fancy, we’ll start the machine/game in an undefined state, so that Step won’t be clickable until the machine is initialized.

Step

Machine Time: {[machineATime]}


Here is the code for the above.

<div ng-init="machineATime=-1">
<button ng-click="machineATime=0">Reset</button>

<button ng-disabled="machineATime < 0" ng-click="machineATime = machineATime + 1">Step</button>


Machine Time: {{machineATime}}

</div>

Experiment B - Add a termination condition and visible machine state

That worked out fine. Let’s try to make this harder for AngularJS by defining a halting condition as reaching a machineTime > 10, where the machine will assume a non-executable state again (requiring a Reset). And to make it more dramatic, let’s show a visible indicator of the machine state (gonna use an <hr/> with style since I don’t know how to draw in HTML.)

Machine Time: {[machineBTime]}

Reset

Step


Here is the code for the above:

<div ng-init="machineBTime=-1">

<hr ng-show="machineBTime >= 0" style="height:25px; background-color:green;"/>
<hr ng-show="machineBTime == -1" style="height:25px; background-color:blue;"/>
<hr ng-show="machineBTime == -2" style="height:25px;; background-color:red"/>

Machine Time: {{machineBTime}}

<button ng-click="machineBTime=0">Reset</button>

<button ng-disabled="machineBTime < 0" ng-click="machineBTime = machineBTime >= 10 ? -2 :machineBTime + 1">Step</button>

</div>

Experiment C - Add a Run command

Still no explicit Javascript to do the above. Let’s see what it takes to get a process to run in the browser. Surely that will require some Javascript coding. We’ll add a Resume command that will continue to call Step until the machine is in a non-runnable state.

Hmmm. It appears impossible to do looping without a controller. Fortunately, I can easily create an inline controller, which I’ll do here inline, just before the UI declaration. Later, I’ll develop this into an AngularJS directive, which will encapsulate and simplify the use of this controller.

Reset

Step Resume


Here is the code for the above:

<script>
function MachineCController($scope, $timeout, $log) {
  $scope.$log = $log;
  $scope.machineState = "undefined";
  $scope.machineTime = 0;
  $scope.machineThread = null;

  $scope.resumeMachine = function() {
    if ($scope.machineThread)
    {
      $scope.$log.warn("resumeMachine() not reentrant.")
    }
    else if ($scope.machineState != "running")
    {
      $scope.$log.warn("resumeMachine() requires a runnable machine.")
    }
    else
    {
      var runner = function() {
        if ($scope.machineState != "running") {
          $timeout.cancel($scope.machineThread);
          $scope.machineThread = null;
        }
        else {
          $scope.stepMachine();
          $scope.machineThread = $timeout(runner, 1000);
        }
      };

      $scope.machineThread = $timeout(runner, 1000);
    }
  };

  $scope.stepMachine = function() {
    if ($scope.machineState != "running") {
      $scope.$log.warn('stepMachine() requires a running machine');
    }
    else {
      $scope.machineTime += 1;
      if ($scope.machineTime > 10)
      {
        $scope.machineState = "finished";
      }
    }
  };

  $scope.pauseMachine = function() {
    if ($scope.machineThread) {
      $timeout.cancel($scope.machineThread);
      $scope.machineThread = null;
    }
  };

  $scope.resetMachine = function() {
    $scope.pauseMachine();
    $scope.machineState = "running";
    $scope.machineTime = 0;
  }

  $scope.isRunning = function() {
    return $scope.machineState == "running";
  }

  $scope.isPaused = function() {
    return $scope.machineThread != null;
  }

  $scope.getMachineTime = function() {
    return $scope.machineTime;
  }

  $scope.getMachineState = function() {
    return $scope.machineState;
  }
}

angular
  .module('BlogApp')
  .controller('MachineCController', MachineCController);
</script>

<div ng-controller="MachineCController">

<hr style="height:25px; width:{{ 25 * (getMachineTime() + 1) }}px; background-color:{{ isRunning() ? \'green\' : \'red\' }};"/>

<h6>Machine Time:{{ getMachineTime() }}</h6>
<h6>Machine State:{{ getMachineState() }}</h6>

<button ng-click="resetMachine()">Reset</button>

<button ng-disabled="!isRunning()" ng-click="stepMachine()">Step</button>
<button ng-disabled="!isRunning() || isPaused()" ng-click="resumeMachine()">Resume</button>

</div>