Thursday, January 5, 2012

F# ≥ C# (Active Pattern & Enum)

F# provides an interesting feature called "active pattern". It converts the input data to something different.  One of the interesting usages is to replace the enum. When I program the enum, I always find frustrated to link enum item to its definition. For example, the following enum define a number enum,
enum Numbers
{
Odd,
Even,
}
but it does not specify what is Odd or Even. I tried to use attribute or simple comments, but what I really want to get is to get the definition in F# when see the enum item. When I see the active pattern, my eyes lit up. It is true you can use a non-partial pattern to solve this problem, but you can't put more than 8 items, so I choose to use partial pattern so my system can be expanded easily in the future.

let ( | Even | _ | ) x = if x % 2 = 0 then Some() else None
let ( | Odd  | _ | ) x = if x % 2 <> 0 then Some() else None
let f x =
match x with
| Even -> "even"
| Odd -> "odd"
let r = f 2  //r = "even"
the above sample only return Some() or None. If want to return something more interesting,

let f0 x = x % 2 = 0
let f1 x = x % 2 <> 0
let ( | Even | _ | ) (x:int) = if f0(x) then Some(sign x) else None
let ( | Odd  | _ | ) (x:int) = if f1(x) then Some(sign x) else None
let f (x:int) =
match x with
| Even sign -> sprintf "even sign=%d" sign
| Odd sign -> sprintf "odd sign=%d" sign
let r = f 2

noticed the highlighted "sign" is to hold the return result Math.Sign(x).

One last thing I want to try is to pass the function with Even (or Odd) pattern. The new code is:

let f0 x = x % 2 = 0
let f1 x = x % 2 <> 0
let ( | Even | _ | ) f (x:int) = if f(x) then Some(sign x) else None
let ( | Odd  | _ | ) f (x:int) = if f(x) then Some(sign x) else None
let f (x:int) =
match x with
| Even f0 (* you can think x passed in here *) sign -> sprintf "even sign=%d" sign
| Odd f1 (* you can think x passed in here *) sign -> sprintf "odd sign=%d" sign
let r = f 2

Please note if you define the pattern Even like the following which take the function f as the second parameter.
let ( | Even | _ | ) (x:int) f = if f(x) then Some(Math.Sign(x)) else None
There will be an error. So the parameter order really matters when you defined the pattern.