2
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)

Maslow
  • 18,464
  • 20
  • 106
  • 193

2 Answers2

3

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.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • 3
    I hadn't noticed that, perhaps it has something to do with provable exhaustive patterns, compiler could be lying if you made a paramaterized active pattern, as it wouldn't necessarily be able to discern if it was truly exhaustive. – Maslow Aug 24 '18 at 13:49
  • 1
    Yes, that is probably a better explanation than mine. Not sure. – Fyodor Soikin Aug 24 '18 at 13:56
  • While I have it open - the spec already is deliberately vague on runtime behavior: `An active pattern function may be executed multiple times against the same pattern input during resolution of a single overall pattern match. The precise number of times that the active pattern function is executed against a particular pattern input is implementation-dependent.` (from paragraph 7.2.3) – scrwtp Aug 26 '18 at 23:16
1

To add to Fyodor's answer, the spec is very explicit in outlining valid active pattern forms (more so than MSDN at least) - see paragraph 7.2.3 Active Patterns for details.

The five valid forms are:

  • Single case - (|CaseName|) inp
  • Partial - (|CaseName|_|) inp
  • Multi-case - (|CaseName1|...|CaseNameN|) inp
  • Single case with parameters - (|CaseName|) arg1 ... argn inp
  • Partial with parameters - (|CaseName|_|) arg1 ... argn inp

Other active pattern functions are not permitted.

What's most relevant here, there's no way to combine a multi-case pattern with additional parameters.

scrwtp
  • 13,437
  • 2
  • 26
  • 30