Search code examples
f#pattern-matchingactive-pattern

Why do active patterns require special syntax?


If ordinary functions could be used as patterns it would save having to write trivial active patterns like

let (|NotEmpty|_|) s = Seq.tryPick Some s

and would, hypothetically, allow

let s = seq []
match s with
| Seq.tryPick Some -> ...
| _ -> //empty

This would make functions more reusable, removing the need for "patternizing" functions you want to use with matching:

let f x = if x then Some() else None
let (|F|_|) = f

I know active patterns may be called as functions, so the previous example could be simplified by defining the pattern only. But forgoing the special pattern syntax simplifies this.

What are the reasons for the special syntax?

EDIT

In the following the active pattern shadows the literal.

[<Literal>]
let X = 1
let (|X|_|) x = if x = 0 then Some() else None

match 0 with //returns true
| X -> true
| _ -> false

Why wouldn't that work for function calls within patterns also?

EDIT 2

I found a scenario that would be ambiguous

let zero n = if n = 0 then Some() else None
match 0 with
| zero -> //function call or capture?

This, in my mind, clarifies why an active pattern must begin with an uppercase letter--it makes the intention clearer and makes shadowing, such as in my previous example, much less likely.


Solution

  • Patterns and let-bound variables have different namespaces, which makes sense most of the time given how frequently shadowing occurs and how frequently short identifiers are used. For example, you might define x on line one of your program and then 200 lines later have match ... with | (x,y) -> x + y, in which case you almost certainly want x to be a fresh identifier.

    If you want to use arbitrary functions, just use a parameterized active pattern:

    let (|Id|_|) f x = f x
    
    match seq [] with
    | Id (Seq.tryPick Some) _ -> ...
    

    EDIT

    See Name Resolution for Patterns in the spec for details on name resolution. The key is that there is a logical PatItems table which is distinct from the ExprItems table that is used for names in expressions. In the particular case that you added to the edit in your question, the last definition of X wins, so it's treated as an active pattern in this case (effectively shadowing the literal when X appears in a pattern).

    In addition to issues with name collisions/shadowing, I suspect that there are also ways that allowing a wider range of expressions in patterns would result in ambiguous parses, though I can't think of any off hand.