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?

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • May just be a bug in the parser; does putting a space between `>>` help? – Brian Jul 21 '11 at 16:44
  • 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|)` – Stephen Swensen Jul 21 '11 at 18:27

2 Answers2

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
| _ -> ()
Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Since the full power of active patterns seems to be their virtual interchangeability with functions, any idea why this limitation exists? – Daniel Jul 21 '11 at 19:01
  • 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. – Daniel Jul 21 '11 at 19:12
  • @Daniel - 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. – Tomas Petricek Jul 21 '11 at 20:51
  • That's what I was thinking, which makes it seem an even more trivial restriction. – Daniel Jul 21 '11 at 20:59
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
    | _ -> ()
Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • 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? – Daniel Jul 21 '11 at 19:04
  • 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... – Stephen Swensen Jul 21 '11 at 19:10
  • You wanted to do that with an active pattern? Or just a function? – Daniel Jul 21 '11 at 19:14
  • 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: http://pastebin.com/QVarC30C – Daniel Jul 21 '11 at 19:47