Search code examples
f#pattern-matchingsystem.reflectionactive-pattern

What's wrong with ActivePattern matching against System.Type?


module Reflection = 
    [<RequireQualifiedAccess>]
    module Type = 
        let isType<'a> = Unchecked.defaultof<'a>
        let (|IsEqual|Isnt|) (_:'a) (t:Type):Choice<unit,unit> =
            let t' = typeof<'a>
            if t = t' then IsEqual else Isnt
        let (|TypeOf|_|) (_:'a) (t:Type) :unit option =
            if t = typeof<'a> then Some ()
            else
                //printfn "did not match %A to %A" typeof<'a> t
                None

open Reflection

match typeof<string> with
// compiles just fine
| Type.TypeOf (Type.isType:int) as x -> Some x.Name
// does not compile
| Type.IsEqual (Type.isType:string) as x -> Some x.Name
| _ -> None

gives Type mismatch. Expecting a Type -> Choice<'a,'b> but given a Type -> 'c -> Choice<unit,unit> The type 'Choice<'a,'b>' does not match the type ''c -> Choice<unit,unit>' (using external F# compiler)


Solution

  • For whatever reason, patterns like this are just simply banned. Only patterns with exactly one result can accept additional parameters.

    This is legal:

    let (|A|) x y = if x = y then 5 else 42
    
    let f (A "foo" a) = printfn "%A" y
    f "foo"  // Prints "5"
    f "bar"  // Prints "42"
    

    And this is legal:

    let (|B|_|) x y = if x = y then Some (y+5) else None
    
    let f = function 
        | B 42 x -> printfn "%d" x 
        | _ -> printfn "try again"
    
    f 42  // prints "47"
    f 5   // prints "try again"
    

    But that's it. All other active patterns must be parameterless. Both of these are illegal:

    let (|A|B|) x y = ...
    let (|A|B|_|) x y = ...
    

    If I had to speculate, I would say that this has to do with predictable runtime performance. When the pattern either matches or not, the compiler can run it exactly once for every parameter value. But if the pattern returns multiple things, and some of those things are present in the match expression, and others don't, and not all of them have the same parameter - it becomes very complicated to figure out the best way to make the minimum amount of function calls.