2

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.

Community
  • 1
  • 1
Daniel
  • 47,404
  • 11
  • 101
  • 179

2 Answers2

1

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.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • Your workaround does something a little different, but it provides good direction. See my answer for a compatible variation. – Daniel Aug 16 '12 at 15:53
  • I think your counterexample is a bit of a straw man. `(|Value|_|)`, or any function generic in its return type, requires that you're mindful of what type is inferred. Your example says more about `+`, namely, that its operands are inferred to be `int`, unless inlined. – Daniel Aug 16 '12 at 15:59
  • Do you also think the following active pattern should be disallowed? `let (|X|) x = unbox x` It's another example of _works in 2.0 but not in 3.0_. – Daniel Aug 16 '12 at 16:04
  • I've changed my example - I hope that this more clearly illustrates the type of mistake that I believe to be problematic. – kvb Aug 16 '12 at 16:43
  • @Daniel - As to your other question - note that I didn't say they should be "disallowed", just that they should usually be avoided. What's the benefit of your `(|X|)` pattern? – kvb Aug 16 '12 at 17:07
  • I wouldn't say `(|X|)` is necessarily useful; it's just a simple example of something you would _expect_ to work. BTW, your updated example is more convincing. – Daniel Aug 16 '12 at 18:26
1

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.

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • This works, but it also does not let you use nested patterns (because the parameter of parameterized active pattern is evaluated expression, rather than a pattern matched against). – Tomas Petricek Aug 16 '12 at 16:23
  • Right. That's the root cause of the different equality semantics, but you put it more concisely. – Daniel Aug 16 '12 at 16:25