Generically Constraining F# – Part III

In the previous post, we talked about some of the generic restrictions that you could do in F#.  I showed some of the basics and how you might use them to your advantage.  We covered the type constraint, null constraint and explicit member constraint.  This time, we’ll cover the rest of them including:

  • Constructor constraint
  • Reference type constraint
  • Enumeration type constraint
  • Delegate type constraint

Constructor contstraint

The constructor constraint in F# is identical to that of C# which states that the specified type must have a default constructor.  In order to use this, we must use the <type> : (new : unit –> <type>).  Let’s walk through a basic example of using this in addition to the type constraint to generate a hash of a given file.

open System
open System.Security.Cryptography

let generateHash<'a when 'a : ( new : unit -> 'a)
                    and  'a :> HashAlgorithm>
 path =
  let algo = new 'a()
  use stream = File.OpenRead path  
  algo.ComputeHash stream
  |> BitConverter.ToString
  |> fun s -> s.Replace("-","")

What this code does is first declares that our type must have a default constructor and must derive from System.Security.Cryptography.HashAlgorithm.  We can create our new generic type much as we would in other languages that support such a feature and then use it to generate our hash.  Let’s verify the behavior by generating an MD5 hash of notepad.exe using the Cryptography Next Generation MD5 class.

> generateHash<MD5Cng> "notepad.exe";;
val it : string = "D3002BDDC758A762736730DE399920E5"

Reference Type Constraint

Much like the constructor constraint, this constraint is also identical to the C# class constraint.  In C#, this restraint is rather oddly named, however, due to the fact that it restricts not only classes, but also interfaces, arrays and delegates.  Instead, F# chose the more apt name of not struct.  Let’s look through an example of using this to initialize a reference type sequence.

let createRefSeq<'a when 'a : not struct> : 'a -> 'a seq =
  Seq.singleton

Now we can verify the behavior of this such as the following:

> createRefSeq "foo";;
val it : seq<string> = seq ["foo"]
> createRefSeq (Func<_>(fun () -> DateTime.Now));;
val it : seq<Func<DateTime>> =
  seq [System.Func`1[System.DateTime] {Method = System.DateTime Invoke();
                                       Target = FSI_0061+it@90-4;}]
> createRefSeq 3;;

  createRefSeq 3;;
  --------------^

stdin(92,15): error FS0001: A generic construct requires that the type 'int'
have reference semantics, but it does not, i.e. it is a struct

Enumeration Constraint

This type constraint, unlike the other two covered in this post cannot be expressed in the current C# version.  We covered this constraint as part of how we could solve Jon Skeet’s problem of Enum.GetValues.  Let’s revisit that solution with a twist of getting the name from a value.

open System

let getEnumName<'a,'b when 'a :> Enum 
                       and 'a :> ValueType
                       and 'a : enum<'b>>
 (value:'a) =
  Enum.GetName(typeof<'a>, value)

And now we can test our behavior such as the following:

> getEnumName DayOfWeek.Saturday;;
val it : string = "Saturday"

Delegate Constraint

Lastly, the delegate constraint constrains the generic type to be a delegate.  This constraint, probably the least used of the constraints, can in fact be put to good use.  In order to use this, we must use the <type> : delegate<args, unit> to declare this restriction.  In my F# First Class Events series, I covered this as a way of adding subscribe capability to F# events in which we can easily subscribe and unsubscribe from an event based upon handing back an IDisposable which on called, removes the event handler.  Let’s go through that solution again.

open System

type IEvent<'Del,'Args when 'Del : delegate<'Args,unit> 
                       and  'Del :> Delegate > with
  member this.Subscribe(d) =
    this.AddHandler(d)
    { new IDisposable with
        member x.Dispose() =
          this.RemoveHandler(d) }

What this allows us to do is add a subscription that we can unsubscribe from easily via the IDisposable object we hand back.  Let’s test this a subscription to a Windows Form Click event and then unsubscribe:

> open System.Windows.Forms
- let f = new Form(Visible=true, TopMost=true)
- let count = ref 0
- let h = f.Click.Subscribe(fun _ _ -> count := !count + 1; printfn "%d" !count);;

val h : IDisposable

1
2
3
4
5
> h.Dispose();;

What we did was create a quick form and then add our handler.  After a few clicks, we get bored, and then decide to dispose of our little counter.  This idea is in keeping with the Reactive Framework which I’ll start on in my next series.

Conclusion

As you can see, the F# language gives us quite a bit of flexibility with generic restrictions.  Going above and beyond what C# already has, we are able to constrain to such things as enum, method signatures, delegates and so on.  Many of these explicit type signatures, although nice to specify, can be inferred by the F# compiler a lot of the time.  With the last segment of first class events, we’ll next step into the Reactive Framework.

No Comments