Pattern combining type test and literal

273 views Asked by At

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.

2

There are 2 answers

2
Daniel On BEST ANSWER

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.

6
kvb On

You should be able to use a parameterized active pattern:

let (|Value|_|) v x = 
    if unbox x = v then 
        Some() 
    else None

The usage should look exactly like what you've got now.

Edit

While I don't know if the breaking change was intentional, I believe that active patterns with generic return types unrelated to the input types should usually be avoided. When combined with type inference, they can easily mask subtle errors. Consider the following example, using your original (|Value|_|) pattern:

match [1] with
| Value [_] -> "Singleton"
| _ -> "Huh?"

It seems like this isn't something you would actually ever attempt - the name implies that Value should only be used with literals; parameterized active patterns enable exactly this scenario.