OCAML functional semantics for the sample skeleton set

We define here the functional semantics for the sample skeleton set presented in SPM lessons by means of Ocaml functions.

Source code

Skeleton.ml
(** this file hosts the functions defining the functional semantics
    of typical algorithmic skeletons 
    @author Marco Danelutto
    @version 1.0 *) 
module Skeleton = 
struct 
 
(** definition of stream parallel skeletons come first, the we have 
    data parallel and control parallel skeleton *) 
 
(** definition of the stream data type. Although Ocaml provides 
    its own stream data type, we define our own in such a way
    functions working on streams can be more easily identified 
    and managed. 
    The type is parametric, in such a way we can define streams of 	
    different types.  *) 
type 'a stream = 
  EmptyStream | Stream of 'a * 'a stream
 
(** definition of the stream parallel generic construct. 
    It is used to support composition of skeletons that otherwise 
    could not be easily achieved. 
    @param f the function to be mapped onto the stream items
    @param s the stream 
    #return the stream of the results *)
let rec streamer f s = 
  match s with 
   EmptyStream -> EmptyStream
|  Stream(x,y) -> Stream((f x),(streamer f y))
 
(** stream parallel skeletons: 
   they are defined as second order functions
   operation on streams is managed by a streamer function *)
 
(** pipeline skeleton: applies stage functions in order. 
    This version just takes 2 functions. Pipeline with more 
    stages may be built out of pipelines of two stages only, 
    E.g. (pipe (pipe f1 f2) (pipe f3 f4)) is a four stage  
    pipeline. 
    @param f the first stage function 
    @param g the second stage function
    @return the function computed by the pipeline, if no input 
       x is given, or the result of applying the pipe to x *) 
let pipe f g = 
  function x -> (g (f x))
 
(** the n stage pipeline: applies all the function in the list
    to the argument, in order. As functions are represented by 
    a list, they should all have the same type. Therefore
    this is not a true pipeline, as it does not model the pipeline
    with stages computing different types of results.
    @param fl the list of the stage functions 
    @param x input 
    @return the function computed by the pipeline, if no input x given, 
	or the result of applying the pipeline to the input x. *)
let rec pipe_l fl x = 
  match fl with
   [] -> x
|  f::rf -> (pipe_l rf (f x))
 
(** farm skeleton: applies a function. 
    @param f the function to be applied
    @return the function computed by the farm (i.e. f), if no 
	parameter x is given, otherwise it returns the result of 
	applying the farm onto the input parameter *)
let farm f = function x -> (f x)
 
(** the farm directly defined on streams.
    Farm f parameter is a function from 'a to 'b. Farm_s parameter f 
    is a function from 'a to 'b as well, this farm processes streams
    and therefore you cannot compose it. As an example: 
    (farm_s (farm_s inc)) is not a correct expression, as it produces
    a farm computing streams of streams, while (farm (farm inc))
    computes correctly and int to int function. 
    @param f the function computed by the farm
    @return the function computed by the farm, if no input data is given,  
    	or the farm computation on the input parameter *) 
let rec farm_s f = 
  function 
    EmptyStream -> EmptyStream
  | Stream (x,y) -> Stream ( (f x),(farm_s f y) )
 
(** this is the stream version of the pipeline. Same comments as for the
    farm stream version above. *)
let rec pipe_s f g = 
  function 
    EmptyStream -> EmptyStream
  | Stream(x,y) -> Stream((g (f x)), (pipe_s f g y))
 
 
(** data parallel skeletons:
   work on arrays (therefore they are implemented
   in terms of array second order functions) 
   In order to operate on streams, they must be
   used as arguments of a streamer call, as for 
   stream parallel skeletons *)
 
(** the map skeleton, defined using library Array.map function 
    @param f the function to be mapped onto the array elements
    *)
let map f = 
  function x -> 
    Array.map f x
 
(** alternative definition of the map skeleton, without taking 
    into account the pre-defined Array.map function 
    @param f the function to be applied to the array items *) 
let map1 f x =
  let len = Array.length x in
  let res = Array.create len (f x.(0)) in
    for i=0 to len-1 do
      res.(i) <- (f x.(i))
    done;
  res
 
(** the reduce skeleton, defined in terms of predefined fold function
    @param f the function to be used to sum up vector elements *)
let reduce f = 
  function x -> 
    let len = Array.length x in 
      Array.fold_right f (Array.sub x 1 (len-1)) x.(0)
 
(** alternative version of the reduce skeleton, not using pre defined 
    functions. The construction of the result array preserves the correct 
    types. 
    @param f the function to be used to sum up vector elements *) 
let rec reduce1 f x =
  let len = Array.length x in
  let res = ref x.(0) in
    for i=1 to len-1 do
      res := (f !res x.(i))
    done;
  !res
 
(** parallel prefix skeleton (also known as scan)
    @param f the function to be used to sum up elements in the array
    *) 
let parallel_prefix f x =
  let len = Array.length x in
  let res = Array.create len x.(0) in
    res.(0) <- x.(0);
    for i=1 to len-1 do
      res.(i) <- (f x.(i) res.(i-1))
    done;
  res
 
(** we define now the stencil data parallel skeleton
    This version only works on vectors. 
    Stencils are defined as lists of indexes to be used 
    to get the stencil items*) 
 
(** returns an array subitem. Index is taken modulo length of the vector
  @param a the array
  @param i the index 
  @return the (i% array lenght)-th element of the array *)
let item a i = 
  let n = Array.length a in
  a.((i+n) mod n)
 
(** computes a stencil out of a stencil index set 
   @param f the function to be applied on the stencil
   @param stencil_indexes the definition of the stencil
   @param a the input array
   @return the result of the stencil data parallel skeleton
  *) 
let stencil f stencil_indexes a =
  let n = (Array.length a) in
  let item a i = a.((i+n) mod n) in
  let rec sten a i = 
    function 
      [] -> []
    | j::rj ->  (item a (i+j))::(sten a i rj) in
  let res = Array.create n (f a.(0) (sten a 0 stencil_indexes)) in
  for i=0 to n-1 do
    res.(i) <- (f a.(i) (sten a i stencil_indexes))
  done;
  res
 
(** the divide and conquer skeleton. 
    @param cs the condition function. If true then split
    @param dc the divide function
    @param bc the base case function
    @param cc the conquer function 
*)
let rec divconq cs dc bc cc x = 
  if(cs x) then (bc x) 
  else (cc (List.map (divconq cs dc bc cc) (dc x)))
 
 
 
(** control skeletons: 
   work on single items, to operate on streams they 
   must be passed as arguments in a streamer call  *)
 
(** the loop_while skeleton. Executes iterations as far as the 
    condition hold true. 
    @param c the condition function
    @param b the loop body function
    @param x the input
    *)
let rec loop_while c b x = 
  match (c x) with 
    true -> (loop_while c b (b x))
  | false -> x
 
(** the for loop skeleton. Executes iterations a controlled amount
    of times. 
    @param init initial value for the iteration variable
    @param last final value of the iteration variable
    @param inc the increment at each step for the iteration variable 
*) 
let rec loop_for init last inc f x = 
  if(init = last) then (f init x) 
  else (loop_for (init+inc) last inc f (f init x))
 
(** used to generate a list of indexes
    @param init the initial value
    @param last the final value
    @param inc the increment value
*) 
let rec inds init last inc = 
  if(init=last) then [init]
  else init::(inds (init+inc) last inc)
 
(** a loop implementation for loops with completely independent 
    iterations. 
    This corresponds to applying a map on the index set. 
    *) 
let loop_forall_indipendent init last inc f x = 
  let iis = (inds init last inc) in 
    let g f x = function i -> (f i x) in 
      List.map (g f x) iis
 
(** the ifthenelse skeleton. 
    @param c the condition function
    @param t the then function
    @param e the else function
*)
let ifthenelse c t e = 
  function x -> match (c x) with
   true -> (t x) 
  | false -> (e x)
 
end;;
Img.ml
(** Sample usage of Skeleton functional semantics
    We use a dummy Image format (array of array of pixels) 
    and we define some functions over images to be used in 
    pipeline stages. 
    Functions are defined as map s *) 
 
open Skeleton
 
(** the type of black and white pixels: levels of gray *)
type bw_pixel = BWP of int;;
(** the type of black and white images: 2 dim array of pixels *)
type bw_image = BWImage of bw_pixel array array;;
 
(** the type of color pixels: RGB *)
type col_pixel = CP of int * int * int;;
(** the type of color images: 2 dim array of pixels *)
type col_image = Image of col_pixel array array;;
 
(** sample image definition *)
let p1 = CP(127,127,127);;
let p2 = CP(0,0,64);;
let p3 = CP(0,64,0);;
let p4 = CP(64,0,0);;
let a = Image [| [| p1; p1; p1; p2; p2 |] ; 
                 [| p2; p3; p3; p4; p1 |] ; 
	         [| p1; p1; p1; p1; p1 |] ; 
		 [| p1; p2; p2; p2; p1 |] |];;
 
(** changes color pixels to black and white *)
let col_to_bw x  = 
  match x with
  CP(r,g,b) -> BWP(r+g+b);;
 
(** kind of smooth pixel *)
let average = 
  function
    CP(x,y,z) -> 
      let n = ((x+y+z)/3) in 
      CP(n,n,n);;
 
(** invert colors *)
let invert = function
  CP(r,g,b) -> CP(255-r, 255-g, 255-b);;
 
 
(** kind of saturation *)
let sq = function
  CP(r,g,b) -> let msq x = ((x*x) mod 256) in 
    CP((msq r), (msq g), (msq b));;
 
(** generic stage: takes a pixel processing function (Color to Color) and 
    returns the stage working on the whole array of arrays
    @param f the function to be applied *)
let stage f = 
  function
    Image x -> Image ( (map (map f)) x);;
 
(** sample stage definitions *)
let stage_average = stage average;;
let stage_sq = stage sq;;
 
(** color to black and white stage: this could not be defined through stage function
    as the type of the output image changes ... *)
let c_to_bw_stage = function
  Image x -> BWImage((map (map col_to_bw)) x);;
 
(** sample main program *)
let main = 
  pipe 
    (pipe (stage invert) (stage average))
    c_to_bw_stage ;;