Towards a more idiomatic FP pricer

Taking advice from a real expert, the pricer becomes more idiomatic

In my quest for a good ocaml book, I found and bought this one: "OCaml for Scientists" by Jon Harrop. I really can't recommend it too highly- it's an excellent book. Furthermore, Jon Harrop looked at this website and gave me some advice about making the code more idiomatic by using record types. He even wrote the first one for me to show how!

The basic idea is that you don't need classes to encapsulate data (as I have been using them), you can just use bindings which become enclosed within functions in a record type. This is very nice indeed.

(** parameter for models *)
type param = {
    integral :       float -> float -> float;
    integral_sq :    float -> float -> float;
    mean :           float -> float -> float;
    rms :            float -> float -> float;
(** Constructor function that makes a parameter record given an
 integral and an integral_sq func *)
let make_param integral integral_sq =
        mean=(fun t1 t2->(integral t1 t2) /. (t2 -. t1));
        rms=(fun t1 t2->(integral_sq t1 t2) /. (t2 -. t1));

(** build a constant parameter *)
let param_const x =
    let integral t1 t2 = (t2-.t1) *. x in
    let x_sq = x *. x in
    let integral_sq t1 t2 = (t2-.t1) *. x_sq in
make_param integral integral_sq;;

(** Parameter that models a linear function f(x) -> ax + b *)
let param_linear a b =
    let integral t1 t2 =
      let integrate_to x = 0.5 *. x *. a *. x +. x *. b in
        integrate_to t2 -. integrate_to t1
    let integral_sq t1 t2 =
        a ** 2. *. (t2 ** 3. -. t1 ** 3.) +.
        a *. b *. (t2 ** 2. -. t1 ** 2.) +.
        b ** 2. *. (t2 -. t1)
    make_param integral integral_sq;;

So our classes have become a simple record of four functions and some functions which return this type. Notice how make_param takes two functions and makes two more from them. The code is otherwise similar to the class-based examples, but is a little more straightforward. For piecewise parameters, we do the same sort of thing:

(** For piecewise constants and piecewise linear functions a "piece" is a
  parameter that applies between low and high limits. *)
type piece = { low: float; high: float; p: param; };;

(** Make piecewise parameters.  Takes a list of pieces which each
  specify the limits and the linear or const parameter in each region.

  At present, does no checking that the pieces are continuous and not
  overlapping *)
let param_piecewise pieces =
  (* there's probably a better way of doing this, but anyway...
     Apply a function to each piece, with the parameters being the part of
     the interval from t1 to t2 that is inside the interval of the piece *)
  let visit_pieces pieces fn t1 t2 =
      let visit_piece piece low high =
        if (low > t2 || high < t1) then
            let range_start = max t1 low in
            let range_end = min t2 high in
            (fn piece) range_start range_end in
      let rec visit_list so_far lis =
        match lis with
            [] -> so_far (** we're done *)
          | {low=low; high=high; p=p} :: rest -> visit_list (so_far +. (visit_piece p low high)) rest
        visit_list 0. pieces
  let integral t1 t2 = visit_pieces pieces (fun x->x.integral) t1 t2 in
  let integral_sq t1 t2 = visit_pieces pieces (fun x->x.integral_sq) t1 t2
  make_param integral integral_sq;;

(** helper func to make parts of a piecewise const function *)
let make_const_piece low high x =
  assert (low<high);
  {low=low; high=high; p=(param_const x)};;

(** helper func to make parts of a piecewise linear function *)
let make_linear_piece low high a b =
  assert (low<high);
  {low=low; high=high; p=(param_linear a b)};;

This really is a great deal nicer than what we had before. Flushed by our success, we make a simple function to turn a list of x,y tuples into a piecewise constant function:

let make_const_param_curve lis =
  let rec build_curve (so_far : piece list) x_old lis =
    match lis with
      [] -> List.rev so_far
    | (x, y)::res when x>x_old ->
        let this_piece = make_const_piece x_old x y in
        build_curve (this_piece::so_far) x res
    | (x, y)::res -> invalid_arg "make_const_param_curve: expects sorted x in list"
    build_curve [] 0. lis;;

Now we can build a piecewise const function by doing:

make_const_param_curve [ 0.1, 0.5; 0.5, 0.25; 1., 0.2 ];;

It would be a simple matter to make a function that built a piecewise linear function up in the same manner.

Unless otherwise specified the contents of this page are copyright © 2015 Sean Hunter. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.