Search code examples
f#f#-3.0active-patternguard-clause

Pattern combining type test and literal


The active pattern in this question fails to compile after upgrading to VS 2012 RTM. It provides a way to do a type test and match a literal within a single pattern. For example:

let (|Value|_|) value = 
  match box value with
  | :? 'T as x -> Some x
  | _ -> None

let getValue (name: string) (r: IDataReader) =
  match r.[name] with
  | null | :? DBNull | Value "" -> Unchecked.defaultof<_>
  | v -> unbox v

Can this be done without the active pattern? I realize a when guard could be used (:? string as s when s = "") but it can't be combined with other patterns.


Solution

  • kvb's variation (which doesn't do quite the same thing since it assumes the type test succeeds) can be modified to produce a similar pattern:

    let (|Value|_|) x value =
      match box value with
      | :? 'T as y when x = y -> Some()
      | _ -> None
    

    However, there is a subtle performance difference. The original active pattern translates to:

    public static FSharpOption<T> |Value|_|<a, T>(a value)
    {
        object obj = value;
        if (!LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
        {
            return null;
        }
        return FSharpOption<T>.Some((T)((object)obj));
    }
    

    that is, it does a type test and cast. It's usage (match x with Value "" -> ...) translates to:

    FSharpOption<string> fSharpOption = MyModule.|Value|_|<object, string>(obj);
    if (fSharpOption != null && string.Equals(fSharpOption.Value, ""))
    {
        ...
    }
    

    Most notably, the typed value returned from the pattern is matched using the typical compiler transformations for patterns (string.Equals for strings).

    The updated pattern translates to:

    public static FSharpOption<Unit> |Value|_|<T, a>(T x, a value)
    {
        object obj = value;
        if (LanguagePrimitives.IntrinsicFunctions.TypeTestGeneric<T>(obj))
        {
            T y = (T)((object)obj);
            T y3 = y;
            if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<T>(x, y3))
            {
                T y2 = (T)((object)obj);
                return FSharpOption<Unit>.Some(null);
            }
        }
        return null;
    }
    

    which uses generic equality and is less efficient than matching against a literal. The usage is a bit simpler since equality is baked into the pattern:

    FSharpOption<Unit> fSharpOption = MyModule.|Value|_|<string, object>("", obj);
    if (fSharpOption != null)
    {
        ...
    }
    

    Anyway, it works. But I like the original better.