Active Patterns 可用於驗證和轉換函式引數

F# 中有趣但非常未知的 Active Patterns 用法是它們可用於驗證和轉換函式引數。

考慮進行引數驗證的經典方法:

// val f : string option -> string option -> string
let f v u =
  let v = defaultArg v "Hello"
  let u = defaultArg u "There"
  v + " " + u

// val g : 'T -> 'T (requires 'T null)
let g v =
  match v with
  | null  -> raise (System.NullReferenceException ())
  | _     -> v.ToString ()

通常,我們在方法中新增程式碼以驗證引數是否正確。在 F# 中使用 Active Patterns 我們可以概括它並在引數宣告中宣告 intent。

以下程式碼等同於上面的程式碼:

let inline (|DefaultArg|) dv ov = defaultArg ov dv

let inline (|NotNull|) v =
  match v with
  | null  -> raise (System.NullReferenceException ())
  | _     -> v

// val f : string option -> string option -> string
let f (DefaultArg "Hello" v) (DefaultArg "There" u) = v + " " + u

// val g : 'T -> string (requires 'T null)
let g (NotNull v) = v.ToString ()

對於函式 fg 的使用者,兩個不同版本之間沒有區別。

printfn "%A" <| f (Some "Test") None  // Prints "Test There"
printfn "%A" <| g "Test"              // Prints "Test"
printfn "%A" <| g null                // Will throw

值得關注的是 Active Patterns 是否會增加效能開銷。讓我們使用 ILSpy 來反編譯 fg 以檢視是否是這種情況。

public static string f(FSharpOption<string> _arg2, FSharpOption<string> _arg1)
{
  return Operators.DefaultArg<string>(_arg2, "Hello") + " " + Operators.DefaultArg<string>(_arg1, "There");
}

public static string g<a>(a _arg1) where a : class
{
  if (_arg1 != null)
  {
    a a = _arg1;
    return a.ToString();
  }
  throw new NullReferenceException();
}

感謝 inline,與經典的引數驗證方法相比,Active Patterns 不會增加額外的開銷。