locked
Partial Active Patterns with multiple choices RRS feed

  • Question

  • Active patterns are a really nice feature but I'm confused about the method for specifying a partial active pattern with multiple choices. It seems strange that the required approach is to have multiple declarations, one for each case, when this is not how it is done for a standard active pattern.

    To give an example, consider the following:

     

    let (|One|Two|) n =
     if n = 1 then One
     elif n = 2 then Two
     else failwith "Not one or two"
    
    let test x =
     match x with
     | One -> printfn "one"
     | Two -> printfn "two"

     


    Currently (|One|Two|) effectively returns a member of the implicitly defined union type equivalent to

     

    type Tmp = One | Two

     

    So far, this is clear. Now imagine the behaviour of raising an exception in the other cases becomes unhelpful to the rest of the program and we wish to make the pattern partial. I would expect to be able to write:

     

    let (|One|Two|_|) n =
     if n = 1 then Some One
     elif n = 2 then Some Two
     else None

     

    where the active pattern is now a function returning Option<Tmp>. The pattern match can perform the option type unwrapping so that the following would be effectively matching against Some One, Some Two, None but we can write One, Two, _ instead for convenience.

     

    let test x =
     match x with
     | One -> printfn "one"
     | Two -> printfn "two"
     | _ -> printfn "other"

     

    Instead, it seems that we must write:

     

    let (|One|_|) n =
     if n = 1 then Some ()
     else None
    
    let (|Two|_|) n =
     if n = 2 then Some ()
     else None
    

     

    which seems clumsy and appears to encourage accidentally writing disjoint or overlapping matches that would be vulnerable to reordering the match cases.

    I note that it is simple enough to rewrite my desired multiple case partial active pattern like the following:

     

    type Tmp = One | Two | Underscore
    
    let onetwo n =
     if n = 1 then One
     elif n = 2 then Two
     else Underscore
    
    let test x =
     match onetwo x with
     | One -> printfn "one"
     | Two -> printfn "two"
     | _ -> printfn "other"

     

    as is the case for every active pattern, but that isn't really an answer.

     

    I assume that it is implemented like this for one of two reasons:

    1. Having different declarations for partial patterns gives a lot more flexibility
    2. It isn't as simple to unwrap the option type as I think for an active pattern

    Can anyone enlighten me on this subject?


    Tuesday, August 2, 2011 2:09 PM

Answers

  • This design decision is covered a bit in the paper "Extensible Pattern Matching Via a Lightweight Language Extension".  Here's the relevant text (from section 2.3):

    For completeness our specification includes structured names with multiple cases, e.g, (|ParseInt|ParseFloat|_|). However we have yet to detect any practical benefit in doing so. One advantage of multiple case patterns in the previous section was that it enabled the compiler to perform completeness analysis on a match block. However this is lost here because of the inherent incompleteness of the pattern. The other advantage of multiple case patterns is that it can give more efficient match evaluation. However this is only effective if different cases share a significant part of the active pattern implementation. In practice we have found this seldom occurs—it is too easy to reuse separate existing parse int and float functions than to write your own code that simultaneously parses an int and a float. For these reasons, partial active patterns with multiple cases are not implemented in the current release of F#.
    Tuesday, August 2, 2011 2:33 PM
    Moderator