ExpandCollapse

+ 1 Imperative Style

Imperative or procedural programming is what most programmers know. Algorithms are constructed as instructions to do something.

  // Newton Integral
  begin
    var c0 = 1.0;
    var c1 = 3.7;
    var c2 = 0.5;
  
    var width = 0.01;
  
    var sum = 0.0;
    var x = 0.005;
    while x < 1.0 do
      var y =  c2 * x * x + c1 * x + c0;
      sum = sum + y * width;
      x = x + width;
    done
  
    println$ "[procedural] Integral is " + sum.str;
  end

You will of course note that even this procedural code contains expressions!

+ 2 Functional Style

Advanced programming languages provide a rich syntax for expressions.

  begin
  println$ "[functonal] Integral is " +
    (
      let val c0 = 1.0 in
      let val c1 = 3.7 in
      let val c2 = 0.5 in
      let val width = 0.01 in
      let fun integral (x:double) (sum:double) =>
        if x < 1.0 then 
          let val y = c2 * x * x + c1 * x + c0 in
          integral (x+width) (sum + y* width)
        else sum
      in integral 0.005 0.0
    ).str
  ;
  end

+ 3 Using a generator

A variant on the functional method using an procedural iterator range and a list comprehension. The iterator is lazy but procedural, the comprehension is expands it.

  begin
  gen range (start:double, bound: double, increment: double) () = {
    var x = start;
    next:>
      yield Some x;
      x = x + increment;
      if x < bound goto next;
    return None[double];
  }
  
  var width = 0.01;
  var xs = range (0.005, 1.0, width);
  
  println$ "[iterator] Integral is " + (
    let val c0 = 1.0 in
    let val c1 = 3.7 in
    let val c2 = 0.5 in
  
    fold_left 
    (fun (sum: double) (x: double) => 
      let val y = c2 * x * x + c1 * x + c0 in
      sum + y * width
    )
    0.0
    (list xs)
  ).str;
  end

+ 4 Using fibres

Fibres, sometimes called f-threads (felix threads), are cooperatively multi-tasked threads which exchange control synchronously using s-channels (synchronous channels).

Fibres and schannels can be used in the so called chips-and-wires model. This model has a major advantage over other programming styles: the components are black boxes, fully isolated and resuable in any context.

This is similar to a function, however functions are one-shot wonders. Fibres, on the other hand, are active running processes.

  begin 
    proc increments 
      (start:double, increment: double) 
      (out: oschannel[double])
      ()
    {
      var x = start;
      while true do
        write$ out, x;
        x = x + increment;
      done
    }
  
    proc integral (out: oschannel[double]) ()
    {
      var c0 = 1.0;
      var c1 = 3.7;
      var c2 = 0.5;
      var width = 0.01;
      var inp, out2 = #mk_ioschannel_pair[double];
      spawn_fthread$ increments (0.005, width) out2;
      var sum = 0.0;
      var x = read inp;
      while x < 1.0 do
        var y = c2 * x * x + c1 * x + c0;
        sum = sum + y * width;
        x = read inp;
      done
      write$ out, sum;
    }
    begin
      var inp, out = #mk_ioschannel_pair[double];
      spawn_fthread$ integral out;
      var sum = read inp;
      println$  "[fibres] Integral is " + sum.str;
    end
  end
   

+ 5 Using synchronous pipelines

This method uses fthreads too, but with special syntax for pipelines. Pipelines only allow linear data flow. However they have the advantage that schannels are not constructed explicitly. This ensures the pipeline will collapse when exhausted.

  include "std/control/spipe";
  begin
    // the source
    proc increments 
      (start: double, increment: double) 
      (out: oschannel[double])
    {
      var x = start;
      while true do
        write$ out, x;
        x = x + increment;
      done
    }
  
   // the transducer
    proc running_integral
    (
      inp: ischannel[double],
      out: oschannel[double * double]
    ) 
    {
      var c0 = 1.0;
      var c1 = 3.7;
      var c2 = 0.5;
      var width = 0.01;
      var sum = 0.0;
      var x = read inp;
      while true do
        var y = c2 * x * x + c1 * x + c0;
        sum = sum + y * width;
        write$ out, (x,sum);
        x = read inp;
      done
    }
  
    // the sink
    proc bound_integral (inp: ischannel[double * double])
    {
      var sum = 0.0;
      var x,nusum = read inp;
      while x < 1.0 do
        sum = nusum;
        x,nusum = read inp;
       done
       println$ "[pipeline] Integral is " + sum.str;
    }
   
    // connect into a pipeline src |-> transducer |-> sink
    var pipeline = increments (0.005, 0.01) |-> running_integral |-> bound_integral;
  
    // run the pipeline
    pipeline;
  
  end

+ 6 Using Coroutines

Fibres provide a conventient medium level abstraction of coroutines. Felix provides low level primitives to implement coroutines as well.

  begin
    var c0 = 1.0;
    var c1 = 3.7;
    var c2 = 0.5;
    var width = 0.01;
  
    var x : double;
    var sum = 0.0;
  
    // program counters for the two coutines
    var psrc: LABEL;
    var psink: LABEL;
  
  
    proc src () 
    {
      x = 0.005;
      // set where the first invocation of the
      // second coroutine will jump to first
      &psrc <- label_address next_x;
      // start the second coroutine
      integrate;
    next_x:>
      x = x + width;
      if x < 1.0 do
        // exchange control with integrator
        branch-and-link (&psink, &psrc); 
        goto next_x;
      done
    }
  
    proc integrate () 
    {
    next_sum:>
      var y = c2 * x * x + c1 * x + c0;
      sum = sum + y * width;
      // exchange control with x-source
      branch-and-link (&psrc, &psink);
      goto next_sum; 
    }
  
    // start the first coroutine
    src;
    println$ "[coroutine] Integral is " + sum.str;
  end