David Ross's Blog Random thoughts of a coder

Tail End Recursion and F# – Part 2

9. June 2009 23:03 by David in

In first post on Tail End Recursion it was shown that recursive functions can be automatically replaced with a fast while loop by the compiler.  The post implies that the head::tail syntax should be used to write F# code instead of using a while loop.  In reality this is often unnecessary due to the pipeline operator |> .  The pipeline operator is used to channel the output of one function into the input parameters of another.  This is similar to a pipe operator in Unix.

ls | grep “my program” | wc –l

In F# the operator allows streams to be filtered and enriched.  I have converted the previous code to use the pipeline operator.  The code function coverts a (x,y) tuple into a (x, y, x*x, y*y, x*y) tuple this holds the temporary working data for the program.  The work data is then summed together and finally the linear regression is calculated.

   1: let public regressionUsingPipelineOperator l : linearRegressionCoeffs =
   2:let length = (float) (List.length l)
   3:  
   4:let working s = 
   5:let (x,y) = s
   6:     (x, y , x*x, y*y, x*y)
   7:  
   8:let calc =
   9:     l |> Seq.map working
  10:       |> Seq.fold (fun s acc ->
  11: let (a,b,c,d,e) = s
  12: let (a1,b1,c1,d1,e1) = acc
  13:                     (a + a1, b + b1, c + c1, d + d1, e + e1)
  14:                  ) (0.0, 0.0, 0.0, 0.0, 0.0) 
  15:       |> fun (sumX, sumY, sumXX, sumYY, sumXY)   ->
  16: let meanX = sumX/length
  17: let meanY = sumY/length
  18: let m = (sumXY - length*meanX*meanY) / (sumXX - length* meanX * meanX)
  19: let c = meanY - m*meanX
  20:            {m = m; c=c;}
  21:
  22:   calc

In the example above the pipeline method is slightly slower than the hand crafted code using the head::tail syntax (since in a single step the code does the equivalent of the map and fold operation).  However the programmer’s intent is far clearer and hence should be used as the default solution.

Update:

The code above can be simplified to by reversing the parameters to the Seq.Fold method as follows.

   1: let public regressionUsingPipelineOperator l : linearRegressionCoeffs =
   2:let length = (float) (List.length l)
   3:  
   4:let calc =
   5:     l|> Seq.fold (fun acc (x,y) ->
   6: let (a,b,c,d,e) = acc
   7:                     (a + x, b + y, c + x*x, d + y*y, e + x*y)
   8:                  ) (0.0, 0.0, 0.0, 0.0, 0.0) 
   9:       |> fun (sumX, sumY, sumXX, sumYY, sumXY)   ->
  10: let meanX = sumX/length
  11: let meanY = sumY/length
  12: let m = (sumXY - length*meanX*meanY) / (sumXX - length* meanX * meanX)
  13: let c = meanY - m*meanX
  14:            {m = m; c=c;}
  15:
  16:   calc

Whoops…  F# code seems to continually shrink.

Comments are closed