Pattern combining type test and literal
Asked Answered
R

2

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.

Roseboro answered 16/8, 2012 at 14:28 Comment(0)
R
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.

Roseboro answered 16/8, 2012 at 15:45 Comment(2)
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).Rheumatoid
Right. That's the root cause of the different equality semantics, but you put it more concisely.Roseboro
D
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.

Decoupage answered 16/8, 2012 at 15:22 Comment(6)
Your workaround does something a little different, but it provides good direction. See my answer for a compatible variation.Roseboro
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.Roseboro
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.Roseboro
I've changed my example - I hope that this more clearly illustrates the type of mistake that I believe to be problematic.Decoupage
@Roseboro - 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?Decoupage
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.Roseboro
R
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.

Roseboro answered 16/8, 2012 at 15:45 Comment(2)
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).Rheumatoid
Right. That's the root cause of the different equality semantics, but you put it more concisely.Roseboro

© 2022 - 2024 — McMap. All rights reserved.