Object Oriented F# - More Extension Everything

In a previous post, I covered a few ways we can do extensions methods, properties, events and so on with F#.  After a few chats, I realized I may have missed a couple of cases that I wanted to cover today.  These two cases are extension operators and extension properties with indexers.

With these examples, I'll try best to show you my development style, using my FsTest library from CodePlex to set up my expectations of the extensions using F# syntax.  If you're not familiar with FsTest, I recently updated it to support the F# September 2008 CTP.


Extension Properties with Indexers

One of the common things we like to have with some of our collections is the ability to retrieve the item by index, using the Item or this property.  With such classes as IEnumerable<'a> among others don't support such a construct, but through the use of extension properties with indexers, they can.  Let's set up how our test should be.

#light
open Xunit
open FsxUnit.Syntax
open ExtensionFSharp.CollectionExtensions

[<Fact>]
let items_with_correct_index_should_be_equal() =
  let items = {0 .. 2 .. 100}
  
  let actual = items.[3]
  let expected = 6
  expected |> should equal actual
 

What I'm going to do is create a collection of 0 to 100 skipping every two numbers.  I will set my expectation that the third item should be six, which should be easy enough to test.  Running this test of course will not work as the extension property isn't defined yet.  Now, let's go ahead and define what that might look like.

#light

namespace ExtensionFSharp

module CollectionExtensions =
  type System.Collections.Generic.IEnumerable with
    
    member this.Item
      with get(index) =
        Seq.nth index this
 

What I was able to do is open the type definition of IEnumerable<'a> and add the Item property, also called the this property in C#, with a get accessor with an index.  This calls the Seq.nth which is a static function on the Seq module to return the item that I want by index.  Now, I can run my test again and check the result.

fstest_extension_property

Just the result we were looking for.  We could go ahead and add additional tests for out of range and inequality, but in the mean time, let's move onto another way of extending F# and the language through operators.


Extension Operators

One of the more interesting ideas is around extension operators.  This would give us the ability to add additional operators to our classes should we feel that they should support add, subtract and so on.  To me, it's a pretty powerful concept to be able to add such things.  Let's set up our test to see how we think it should look.

#light

open ExtensionFSharp.DrawingExtensions
open Xunit
open FsxUnit.Syntax
open System.Drawing

[<Fact>]
let red_and_blue_should_equal_magenta() =
  let red = Color.Red
  let blue = Color.Blue
  let expected = red + blue
  
  actual.R |> should equal expected.R
  actual.G |> should equal expected.G
  actual.B |> should equal expected.B
 

Our simple test is to determine if the RGB from adding red and blue should equal to magenta.  Once we try to compile, of course it doesn't work yet because we haven't defined the operator extension yet.  Now, let's implement the operator extension and see if that works.

#light

namespace ExtensionFSharp

open System
open System.Drawing

module DrawingExtensions = 
  
  type System.Drawing.Color with
    static member (+) (c1:Color, c2:Color) =
      let r = Convert.ToInt32(c1.R + c2.R)
      let g = Convert.ToInt32(c1.G + c2.G)
      let b = Convert.ToInt32(c1.B + c2.B)
      Color.FromArgb(r, g, b)

What we're going to do is define the operator two take two colors and then add the red, green and blue, and then create a new one from this.  We have the extension operator now defined, but going to compile it, it still won't work.  It turns out that operator extensions are not supported.  Now is the perfect time to let Don Syme know if you want them in there. 

What options do we have then for operators?  The easiest answer is to define a custom global operator should you want that capability.  Let's define a customer operator, +$ to be the operator to handle adding of two colors together.  The first thing we need to do is change our test.

#light

open ExtensionFSharp.DrawingExtensions

open Xunit
open FsxUnit.Syntax
open System.Drawing

[<Fact>]
let red_and_blue_should_equal_magenta() =
  let red = Color.Red
  let blue = Color.Blue
  let actual = red +$ blue
  let expected = Color.Magenta

  actual.R |> should equal expected.R
  actual.G |> should equal expected.G
  actual.B |> should equal expected.B
 

The test is now changed, so let's reflect that in our new custom operator.  Once again, we'll define it inside our DrawingExtensions module in order to keep it scoped to our drawing area.

#light

namespace ExtensionFSharp

open System
open System.Drawing

module DrawingExtensions = 
  let (+$) (c1:Color) (c2:Color) =
    let r = Convert.ToInt32(c1.R + c2.R)
    let g = Convert.ToInt32(c1.G + c2.G)
    let b = Convert.ToInt32(c1.B + c2.B)
    Color.FromArgb(r, g, b)

Once we are able to compile and run our test, it should give us the green light like this.

fstest_extension_operator

Once again, just the result we've been looking for.  Not the ideal situation when I'd rather extend the type to include another operator instead of more of a global operator.  Other options could just be extending the Color class to include either a static Color.Add method or an instance Add method.  Either would work, but I would prefer the operator extension.


Wrapping It Up

Extension everything in F# is pretty powerful to allow us to not only create extension methods like C#, but properties, static methods, events, and indexed properties.  I would prefer to see extension operators supported as well in an upcoming CTP release as well.  I'd suggest that you let Don know if you feel the same way.  In the upcoming series, I intend to cover more object oriented principles as explained in F#, such as interfaces, classes, structs, but also the principles we hold dear such the Open/Closed Principle and so on.



kick it on DotNetKicks.com

No Comments