So you have an Object Oriented library but yet want to be able to use F#’s functional pipelining feature to design expressive data processing workflows.  How do you go about it?

First, lets set a goal.  Some low hanging fruit so to speak.  Let’s pretend we have a set of images we want to load, resize, intensify and save as Png files for later use in an online image gallery.

 

Image Processing In C#

For reference, this would look something like the following using our DotImage toolkit in C#:

public void ProcessImage(string fromfile, string tofile)

{

    ImageCommand[] commands = new ImageCommand[] {

        new ChangePixelFormatCommand( PixelFormat.Pixel8bppIndexed ),

        new ResampleCommand( new Size( 800, 600) ),

        new IntensifyCommand( 50.0 )

    };

 

    AtalaImage currentImage =  new AtalaImage(fromfile);

    foreach (var command in commands)

    {

        currentImage = command.Apply(currentImage).Image;

    }

 

    currentImage.Save(tofile, new PngEncoder(), null);  

}

It’s a testament to the skill of our Senior Architect that this task is as simple as it is in C#. 

Note: our image representation is IDisposable and for optimal performance should immediately be disposed when done being used.  I’ll be covering how to leverage F#’s type system to handle this in a later post.

 

Now, in F#

In comparison, this is how I envision this same process using F#’s functional pipelining style:

let processImage infile outfile =

  Image.fromFile infile

  |> Image.changePixelFormat Image.PixelFormat.Pixel8bppIndexed

  |> Image.resample 800 600

  |> Image.intensify 50.0

  |> Image.toPngFile outfile

These pipelined functions are so easy on the eyes.  It’s immediately obvious what’s going on here.  Unfortunately, it can be rather difficult to use pipelining with non function constructs.

 

Wrapping an Object Oriented Library for Pipelining

To bring this seamless integration with F#, we must first wrap these Object Oriented classes so that they can be used in a functional way.  This is a rather simple task:

namespace Atalasoft.FSharp

 

open System.Drawing

 

open Atalasoft.Imaging

open Atalasoft.Imaging.Codec

open Atalasoft.Imaging.ImageProcessing

open Atalasoft.Imaging.ImageProcessing.Filters

 

module Image =

 

  type image = Atalasoft.Imaging.AtalaImage

  type PixelFormat = Atalasoft.Imaging.PixelFormat

 

  let fromFile (filename: string) =

    new image( filename )

 

  let toPngFile (filename: string) (img: image) =

    img.Save( filename, new PngEncoder(), null)  |> ignore

 

  let resample (width: int) (height: int) (img: image) =

    let newSize = new Size( width, height ) in

      let cmd = new ResampleCommand( newSize ) in

        cmd.Apply(img).Image   

 

  let intensify (magnitude: double) (img: image) =

    let cmd = new IntensifyCommand( magnitude ) in

      cmd.Apply(img).Image

 

  let changePixelFormat (pf: PixelFormat) (img: image) =

    let changer = new AtalaPixelFormatChanger() in

      changer.ChangePixelFormat(img, pf, null)

 

So, in this way, we can define modules which will hide our object oriented interfaces.  However, let’s take a deeper look.  There are a some important things to keep in mind when designing functions for pipelining. 

First, observe that the module definition encapsulates all of our pipelining functions and mandates how they will be accessed later.  It is good design practice to define all pipelining functions for the same type within one module.  This module should have the same name as the type but with the first letter capitalized.

Second, notice that we use type abbreviations to create local versions of the AtalaImage and PixelFormat types.  This makes our library code easier to read and allows us to use the F# lowercase type naming style.   Even more importantly, by defining all exposed types in this way the consumer of this module will not need to open any namespaces from the assemblies we are wrapping.

Third, see how image is the last parameter to each function?  To be able to pipeline into a function its final parameter must be of the to-be-pipelined type.

Note: This is not strictly true.  If you wish to pipeline into a function and then return a function with that value curried in, it can have additional parameters which will be filled in later. However, that’s a topic best left for another time.

Finally, note that the return type of each of the intermediate image processing functions is also image.  This ensures that the output can be pipelined directly into the next function.

 

Our F# Wrapper in Action

Let’s highlight the contents of this fsx file, hit alt-enter, and give it a whirl in the F# Interactive Window:

#r "Atalasoft.dotImage"

#r "Atalasoft.dotImage.Lib"

#r "Atalasoft.Shared"

 

#load "Image.fs"

 

open Atalasoft.FSharp

 

let processImage infile outfile =

  Image.fromFile infile

  |> Image.changePixelFormat Image.PixelFormat.Pixel32bppBgr

  |> Image.resample 400 300

  |> Image.intensify 70.0

  |> Image.toPngFile outfile

 

Finally, in F# Interactive we can simply type:

  > processImage @"C:\temp\Water lilies.jpg" @"C:\temp\Water lilies.png";;
  val it : unit = ()

and we have our processed image:

 Water lilies

 

Conclusion

Of course, this implementation leaves much to be desired.  It hides the vast majority of the available functionality in our underlying library.  Taken to it’s natural end it leaves us with the unfortunate task of wrapping our entire library one command at a time, which is hardly an appealing prospect.

What if you wish to leverage existing objects without wrapping each individually?  Well, we will explore that next time.