val isBob : name:string -> string

Full name: Index.isBob
val name : string
val isName1 : matchName:string -> personName:'a -> string

Full name: Index.isName1
val matchName : string
val personName : 'a
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val isName2 : matchName:string -> personName:'a -> string (requires equality)

Full name: Index.isName2
val personName : 'a (requires equality)
val str : 'a (requires equality)
val expected : 'a (requires equality)
val value : 'a (requires equality)
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val niceIsName : matchName:string -> personName:string -> string

Full name: Index.niceIsName
val personName : string
active recognizer Eq: 'a -> 'a -> unit option

Full name: Index.( |Eq|_| )
namespace System
val dbForwarder : str:'a -> unit

Full name: Index.dbForwarder
val str : 'a
val consoleForwarder : str:'a -> unit

Full name: Index.consoleForwarder
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
val makeForwarder : _arg1:Map<string,string> -> unit

Full name: Index.makeForwarder
active recognizer Val: 'a -> Map<'a,'b> -> 'b option

Full name: Index.( |Val|_| )
val conn : string
val prefix : string
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val s : string
type Int64 =
  struct
    member CompareTo : value:obj -> int + 1 overload
    member Equals : obj:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val MaxValue : int64
    static val MinValue : int64
    static member Parse : s:string -> int64 + 3 overloads
    static member TryParse : s:string * result:int64 -> bool + 1 overload
  end

Full name: System.Int64
Int64.TryParse(s: string, result: byref<int64>) : bool
Int64.TryParse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<int64>) : bool
val v : int64
active recognizer AsInt64: string -> int64 option

Full name: Index.( |AsInt64|_| )
val x : int64
val tryGetPort : hostName:'a -> config:Map<'a,string> -> int64 option (requires comparison)

Full name: Index.tryGetPort
val hostName : 'a (requires comparison)
val config : Map<'a,string> (requires comparison)
val portNum : int64
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Quotations
module ExprShape

from Microsoft.FSharp.Quotations
val expr : Expr

Full name: Index.expr
val traverse : expr:Expr -> Expr

Full name: Index.traverse
val expr : Expr
active recognizer ShapeVar: Expr -> Choice<Var,(Var * Expr),(obj * Expr list)>

Full name: Microsoft.FSharp.Quotations.ExprShape.( |ShapeVar|ShapeLambda|ShapeCombination| )
val v : Var
Multiple items
type Expr =
  override Equals : obj:obj -> bool
  member GetFreeVars : unit -> seq<Var>
  member Substitute : substitution:(Var -> Expr option) -> Expr
  member ToString : full:bool -> string
  member CustomAttributes : Expr list
  member Type : Type
  static member AddressOf : target:Expr -> Expr
  static member AddressSet : target:Expr * value:Expr -> Expr
  static member Application : functionExpr:Expr * argument:Expr -> Expr
  static member Applications : functionExpr:Expr * arguments:Expr list list -> Expr
  ...

Full name: Microsoft.FSharp.Quotations.Expr

--------------------
type Expr<'T> =
  inherit Expr
  member Raw : Expr

Full name: Microsoft.FSharp.Quotations.Expr<_>
static member Expr.Var : variable:Var -> Expr
active recognizer ShapeLambda: Expr -> Choice<Var,(Var * Expr),(obj * Expr list)>

Full name: Microsoft.FSharp.Quotations.ExprShape.( |ShapeVar|ShapeLambda|ShapeCombination| )
val body : Expr
static member Expr.Lambda : parameter:Var * body:Expr -> Expr
active recognizer ShapeCombination: Expr -> Choice<Var,(Var * Expr),(obj * Expr list)>

Full name: Microsoft.FSharp.Quotations.ExprShape.( |ShapeVar|ShapeLambda|ShapeCombination| )
val a : obj
val args : Expr list
val traversed : Expr list
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val RebuildShapeCombination : shape:obj * arguments:Expr list -> Expr

Full name: Microsoft.FSharp.Quotations.ExprShape.RebuildShapeCombination
val result : string

Full name: Index.result
namespace Swensen
namespace Swensen.Unquote
module Assertions

from Swensen.Unquote
val test1 : unit -> unit

Full name: Index.test1
val test : expr:Expr<bool> -> unit

Full name: Swensen.Unquote.Assertions.test
val test2 : unit -> unit

Full name: Index.test2
namespace FSharpComposableQuery
type Person =
  {name: string;
   age: int;}

Full name: Index.Person
Person.name: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
Person.age: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val people : Person list

Full name: Index.people
val aQuery : Person

Full name: Index.aQuery
val query : QueryImpl.QueryBuilder

Full name: FSharpComposableQuery.TopLevelValues.query
val p : Person
custom operation: sortBy ('Key)

Calls Linq.QueryBuilder.SortBy
custom operation: last

Calls Linq.QueryBuilder.Last
val filter : Expr<(Person -> bool)>

Full name: Index.filter
val q2 : filter:Expr<(Person -> bool)> -> seq<Person>

Full name: Index.q2
val filter : Expr<(Person -> bool)>
val filtered : seq<Person>

Full name: Index.filtered
Multiple items
type OptionBuilder =
  new : unit -> OptionBuilder
  member Bind : m:'b option * f:('b -> 'c option) -> 'c option
  member Return : value:'a -> 'a option

Full name: Index.OptionBuilder

--------------------
new : unit -> OptionBuilder
val x : OptionBuilder
member OptionBuilder.Bind : m:'b option * f:('b -> 'c option) -> 'c option

Full name: Index.OptionBuilder.Bind
val m : 'b option
val f : ('b -> 'c option)
val value : 'b
member OptionBuilder.Return : value:'a -> 'a option

Full name: Index.OptionBuilder.Return
val value : 'a
Multiple items
val option : OptionBuilder

Full name: Index.option

--------------------
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val maybeAdd : maybeA:int option -> maybeB:int option -> int option

Full name: Index.maybeAdd
val maybeA : int option
val maybeB : int option
val a : int
val b : int
namespace Hopac
val workToDo : Job<int>

Full name: Index.workToDo
val job : JobBuilder

Full name: Hopac.TopLevel.job
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val workDone : int

Full name: Index.workDone
type Job<'T> =

Full name: Hopac.Job<_>
module Global

from Hopac.Job
val run : Job<'x> -> 'x

Full name: Hopac.Job.Global.run
type AsyncBuilder =
  private new : unit -> AsyncBuilder
  member Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
  member Combine : computation1:Async<unit> * computation2:Async<'T> -> Async<'T>
  member Delay : generator:(unit -> Async<'T>) -> Async<'T>
  member For : sequence:seq<'T> * body:('T -> Async<unit>) -> Async<unit>
  member Return : value:'T -> Async<'T>
  member ReturnFrom : computation:Async<'T> -> Async<'T>
  member TryFinally : computation:Async<'T> * compensation:(unit -> unit) -> Async<'T>
  member TryWith : computation:Async<'T> * catchHandler:(exn -> Async<'T>) -> Async<'T>
  member Using : resource:'T * binder:('T -> Async<'U>) -> Async<'U> (requires 'T :> IDisposable)
  ...

Full name: Microsoft.FSharp.Control.AsyncBuilder
val x : AsyncBuilder
member AsyncBuilder.Bind : aJob:Job<'a> * f:('a -> Async<'b>) -> Async<'b>

Full name: Index.Bind
val aJob : Job<'a>
val f : ('a -> Async<'b>)
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
val jobResult : 'a
module Extensions

from Hopac
module Async

from Hopac.Extensions
module Global

from Hopac.Extensions.Async
val ofJob : Job<'x> -> Async<'x>

Full name: Hopac.Extensions.Async.Global.ofJob
val asyncWorkToDo : Async<int>

Full name: Index.asyncWorkToDo
val asyncWorkDone : int

Full name: Index.asyncWorkDone
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
namespace FSharp.Text
namespace FSharp.Text.RegexProvider
type MyRegex = Regex<...>

Full name: Index.MyRegex
type Regex =
  inherit Regex
  member MatchTimeout : TimeSpan
  member Options : RegexOptions
  member RightToLeft : bool
  val capnames : Hashtable
  val caps : Hashtable
  val capsize : int
  val capslist : string []
  val code : RegexCode
  val factory : RegexRunnerFactory
  ...

Full name: FSharp.Text.RegexProvider.Regex
val regexed : Regex<...>.MatchType2

Full name: Index.regexed
val bobMcBob : Person

Full name: Index.bobMcBob
property Regex<...>.MatchType2.name: Text.RegularExpressions.Group


Gets the "name" group from this match
property Text.RegularExpressions.Capture.Value: string
property Regex<...>.MatchType2.age: Text.RegularExpressions.Group


Gets the "age" group from this match
type Int32 =
  struct
    member CompareTo : value:obj -> int + 1 overload
    member Equals : obj:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member GetTypeCode : unit -> TypeCode
    member ToString : unit -> string + 3 overloads
    static val MaxValue : int
    static val MinValue : int
    static member Parse : s:string -> int + 3 overloads
    static member TryParse : s:string * result:int -> bool + 1 overload
  end

Full name: System.Int32
Int32.Parse(s: string) : int
Int32.Parse(s: string, provider: IFormatProvider) : int
Int32.Parse(s: string, style: Globalization.NumberStyles) : int
Int32.Parse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider) : int
val str : string
Regex<...>.IsMatch(input: string) : bool


Indicates whether the regular expression finds a match in the specified input string

Text.RegularExpressions.Regex.IsMatch(input: string, pattern: string) : bool
Text.RegularExpressions.Regex.IsMatch(input: string, pattern: string, options: Text.RegularExpressions.RegexOptions) : bool
Text.RegularExpressions.Regex.IsMatch(input: string, pattern: string, options: Text.RegularExpressions.RegexOptions, matchTimeout: TimeSpan) : bool
Int32.TryParse(s: string, result: byref<int>) : bool
Int32.TryParse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<int>) : bool
val i : int
active recognizer IsMatch: string -> Choice<Regex<...>.MatchType2,unit>

Full name: Index.( |IsMatch|NotMatch| )
val regexed : Regex<...>.MatchType2
active recognizer AsInt: string -> int option

Full name: Index.( |AsInt|_| )
active recognizer NotMatch: string -> Choice<Regex<...>.MatchType2,unit>

Full name: Index.( |IsMatch|NotMatch| )

tower

Solving Real World Problems...

...from the Ivory Tower

Active Patterns

Handy helpers

Liberally stolen from this gist.

Thanks Karl Nilsson!

1: 
2: 
3: 
4: 
let isBob name =
  match name with
  | "bob" -> "It's bob!"
  | _ -> "It's not bob!"

What about writing an isName function?

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let isName1 matchName personName =
  match personName with
  | personName ->
    sprintf "It's %s!" matchName
  | _ ->
    // This rule will never be matched...
    sprintf "It's not %s!" matchName
1: 
2: 
3: 
4: 
5: 
6: 
let isName2 matchName personName =
  match personName with
  | str when str = personName ->
    sprintf "It's %s!" matchName
  | _ ->
    sprintf "It's not %s!" matchName
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let (|Eq|_|) expected value =
  if expected = value then Some ()
  else None

let niceIsName matchName personName =
  match personName with
  | Eq matchName ->
    sprintf "It's %s!" matchName
  | _ ->
    sprintf "It's not %s!" matchName
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let (|Val|_|) = Map.tryFind

let makeForwarder =
  function
  | Val "type" "db" & Val "connection" conn ->
    dbForwarder conn 
  | Val "type" "console" & Val "prefix" prefix ->
    consoleForwarder prefix
  | _ -> failwith "wat?"

let (|AsInt64|_|) s =
  match Int64.TryParse s with
  | true, v -> Some v
  | _ -> None

match "1234" with
| AsInt64 x -> "ok"
| _ -> "not ok"

Put 'em together

1: 
2: 
3: 
4: 
let tryGetPort hostName config =
  match config with
  | Val hostName (AsInt64 portNum) -> Some portNum
  | _ -> None

Partitioning existing data

F# Quotations represent the AST of a piece of F# code; nodes in the tree can be one of 38 cases

Let's say we want to transform if statements and carry everything else through unchanged

That's 38 lines of code, or...

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
open FSharp.Quotations
open FSharp.Quotations.ExprShape

let expr = <@@ if 10 < 5 then "really?!" else "good" @@>

let rec traverse expr =
  match expr with
  | ShapeVar (v) ->
    Expr.Var(v)
  | ShapeLambda(v, body) ->
    Expr.Lambda(v, traverse body)
  | ShapeCombination(a, args) ->
    let traversed = args |> List.map traverse
    ExprShape.RebuildShapeCombination(a, traversed)

let result =
  if expr = traverse expr then "Hurrah!" else "Boo!"

result:

"Hurrah!"

Derived patterns are part of FSharp.Core

Quotations

Unquote

Not just failing, but failing with style

1: 
2: 
3: 
4: 
5: 
6: 
open Swensen.Unquote.Assertions

let test1() =
  test <@ 1 = 2 @>

test1()
Test failed:

1 = 2
false
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let test2() =
  test <@
    [3;2;1;0]
    |> List.map ((+) 1)
    |> (=) [1 + 3..1 + 0]
  @>

test2()
Test failed:

[3; 2; 1; 0] |> List.map ((+) 1) |> (=) [1 + 3..1 + 0]
[4; 3; 2; 1] |> (=) [4..1]
[4; 3; 2; 1] |> (=) []
false

FSharp.Linq.ComposableQuery

(SkillsCast from Philip Wadler)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open FSharpComposableQuery

type Person = { name: string; age: int }

let people = [
    { name = "bob"; age = 25 }
    { name = "fred"; age = 27 }
  ]

let aQuery =
  query {
    for p in people do
      sortBy p.age
      last
  }
{name = "fred";
 age = 27;}
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let filter = <@ fun (p : Person) -> p.age < 27 @>

let q2 filter =
  query {
    for p in people do
      if (%filter) p then yield p
  }

let filtered = q2 filter
seq [{name = "bob";
      age = 25;}]

Computational Expressions

The basics

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
type OptionBuilder () =
  member x.Bind(m, f) =
    match m with
    | Some value -> f value
    | None -> None
  member x.Return value =
    Some value

let option = OptionBuilder()

let maybeAdd maybeA maybeB =
  option {
    let! a = maybeA
    let! b = maybeB
    return a + b
  }

// None
maybeAdd (Some 10) None

// Some 20
maybeAdd (Some 10) (Some 10)

Excellent tutorial on Scott's blog

Lots of examples in FSharpx.Extras and ExtCore

Hopac

Playing nice with your friends

(Also check out Marcus Griep's blog)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open Hopac

let workToDo = job {
    let! a = job { return 10 }
    let! b = async { return 10 }
    return a + b
  }

let workDone = Job.Global.run workToDo
20
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type AsyncBuilder with
  member x.Bind(aJob : Job<'a>, f : 'a -> Async<'b>) =
    job {
      let! jobResult = aJob
      return! f jobResult
    } |> Extensions.Async.Global.ofJob

let asyncWorkToDo = async {
    let! a = job { return 10 }
    let! b = async { return 10 }
    return a + b
  }

let asyncWorkDone = asyncWorkToDo |> Async.RunSynchronously
20

See my recent blog post for more details

Type Providers

FSharp.Text.RegexProvider

Regex with one less problem

Also: text and video type provider tutorials on my blog

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open FSharp.Text.RegexProvider

type MyRegex = Regex<"""(?<name>.+):\s+(?<age>\d+)""">

let regexed = MyRegex().Match("Bob McBob: 42")

let bobMcBob =
  { name = regexed.name.Value
    age = regexed.age.Value |> Int32.Parse }
{name = "Bob McBob";
 age = 42;}
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let (|IsMatch|NotMatch|) str =
  match MyRegex.IsMatch str with
  | true -> IsMatch (MyRegex().Match(str))
  | false -> NotMatch

let (|AsInt|_|) str =
  match Int32.TryParse str with
  | true, i -> Some i
  | false, _ -> None

let (|StringToPerson|_|) str =
  match str with
  | IsMatch regexed ->
    match regexed.age.Value with
    | AsInt i -> { name = regexed.name.Value
                   age = i } |> Some
    | _ -> None
  | NotMatch -> None

Thanks

Michael Newton (@mavnn)

More at http://blog.mavnn.co.uk

Training and consultancy through http://mavnn.co.uk