Use of typeof<_> in active pattern
Asked Answered
A

2

5

Given the following contrived active pattern:

let (|TypeDef|_|) (typeDef:Type) (value:obj) =
  if obj.ReferenceEquals(value, null) then None
  else
    let typ = value.GetType()
    if typ.IsGenericType && typ.GetGenericTypeDefinition() = typeDef then Some(typ.GetGenericArguments())
    else None

The following:

let dict = System.Collections.Generic.Dictionary<string,obj>()
match dict with
| TypeDef typedefof<Dictionary<_,_>> typeArgs -> printfn "%A" typeArgs
| _ -> ()

gives the error:

Unexpected type application in pattern matching. Expected '->' or other token.

But this works:

let typ = typedefof<Dictionary<_,_>>
match dict with
| TypeDef typ typeArgs -> printfn "%A" typeArgs
| _ -> ()

Why is typedefof (or typeof) not allowed here?

Ability answered 21/7, 2011 at 16:37 Comment(2)
May just be a bug in the parser; does putting a space between >> help?Diamine
Just to be clear and to avoid "This rule will never match" confusion, is TypeDef supposed to be a partial active pattern? i.e. (|TypeDef|_|) instead of (|TypeDef|)Ordovician
A
5

Even if you're using a parameterized active pattern (where the argument is some expression), the compiler parses the argument as a pattern (as opposed to an expression), so the syntax is more restricted.

I think this is essentially the same problem as the one discussed here: How can I pass complex expression to parametrized active pattern? (I'm not sure about the actual compiler implementation, but the F# specification says that it should parse as a pattern).

As a workaround, you can write any expression inside a quotation, so you could do this:

let undef<'T> : 'T = Unchecked.defaultof<_>

let (|TypeDef|) (typeExpr:Expr) (value:obj) =
  let typeDef = typeExpr.Type.GetGenericTypeDefinition()
  // ...

let dict = System.Collections.Generic.Dictionary<string,obj>()
match dict with
| TypeDef <@ undef<Dictionary<_,_>> @> typeArgs -> printfn "%A" typeArgs
| _ -> ()
Antipyrine answered 21/7, 2011 at 17:45 Comment(4)
Since the full power of active patterns seems to be their virtual interchangeability with functions, any idea why this limitation exists?Ability
I'm stumped why an expression is valid here, but not a function call. Is it because an expression is constant? I'm not sure how typeof in C# is implemented, but it doesn't seem to be a function as it is in F#. I'm guessing if F#'s typeof was implemented similarly this might work, since it would behave more like a constant.Ability
@Ability - I think this is just a syntactic restriction. The code that Stephen posted can be parsed as a pattern: TypeDef (null:Dictionary<_,_>) typeArgs (with the type specification being a type annotation). Calling a function with generic type arguments (like typedefof<...>) cannot be parsed as a pattern.Antipyrine
That's what I was thinking, which makes it seem an even more trivial restriction.Ability
O
5

Adding to Tomas' answer, the troublesome syntax in this case appears to be with the explicit type arguments. Another workaround is to use a dummy parameter to transmit the type information

let (|TypeDef|_|) (_:'a) (value:obj) =
  let typeDef = typedefof<'a>
  if obj.ReferenceEquals(value, null) then None
  else
    let typ = value.GetType()
    if typ.IsGenericType && typ.GetGenericTypeDefinition() = typeDef then Some(typ.GetGenericArguments())
    else None

let z = 
    let dict = System.Collections.Generic.Dictionary<string,obj>()
    match dict with
    | TypeDef (null:Dictionary<_,_>) typeArgs -> printfn "%A" typeArgs
    | _ -> ()
Ordovician answered 21/7, 2011 at 18:37 Comment(4)
Thanks. Nice workaround. I'd like to better understand the limitations and general-purpose workarounds. For instance, what if you wanted to pass the result of a function call as a parameter to the active pattern?Ability
You're welcome. Indeed, I was looking into making a function which could take a function like f<'a> : int -> int and turn it into a function of type 'a -> int -> int, but I found either you can't, or I don't know how, to indicate that a function has explicit type arguments through type annotations...Ordovician
You wanted to do that with an active pattern? Or just a function?Ability
I don't think there's a way to do it without exposing the type parameter, either in the return value or through an argument. You can, however, bundle such a type parameter by using a DU, for passing to other functions. Take a look at this: pastebin.com/QVarC30CAbility

© 2022 - 2024 — McMap. All rights reserved.