Passing partial active patterns as arguments?
Asked Answered
F

1

9

I'm learning F# by writing a recursive descent parser using active patterns.

Since all my rules or partial active patterns I need to combine them in different manners, but I'm getting really frustrated with the syntax of passing active patterns as parameters.

The following example shows the trouble I'm having:

// Combines two patterns by chaining them.
let (|Chain|_|) (|Pattern1|_|) (* Should I use pipes here? *) (|Pattern2|_|) data =
    match data with
    |Pattern1 result ->
        match result with
        |Pattern2 result2 -> Some result2
        |_ -> None
    |_ -> None 

// Stupid test patterns
let (|IfBiggerThan10ThenDouble|_|) value = if value > 10 then Some (value*2) else None
let (|IfLessThan100ThenDouble|_ |) value = if value < 100 then Some (value*2) else None

match 20 with
// Do I need pipes here?
|Chain (IfBiggerThan10ThenDouble IfLessThan100ThenDouble) value -> printfn "%A" value // Should print 80
| _ -> printfn "Did not match"

My main confusion seems to be about the '|' operator. Sometimes it seems to be a part of the type of the pattern and sometimes part of the name.

Frederiksen answered 25/3, 2014 at 13:14 Comment(0)
T
14

You do not really need to implement your own chaining of patterns, because you can directly nest the patterns which gives you the required result:

match 20 with
| IfBiggerThan10ThenDouble(IfLessThan100ThenDouble value) -> printfn "%A" value
| _ -> printfn "Did not match"

This will first call the IfBiggerThan10ThenDouble pattern which calculates 20*2 and passes the value to the nested pattern IfLessThan100ThenDouble. This again doubles the value and binds it to the value symbol (when it succeeds).

That said, your implementation of the Chain pattern actually works and can be called like this:

match 20 with
| Chain (|IfBiggerThan10ThenDouble|_|) (|IfLessThan100ThenDouble|_|) value -> 
    printfn "%A" value // Should print 80
| _ -> printfn "Did not match"

In general, active pattern (|P|_|) is really just a function with a special name. You can treat it as an ordinary function and call it by writing (|P|_|) argument or you can treat it as a value and pass it as an argument to other functions or parameterized active patterns. Your code would work if you implemented Chain as a pattern taking ordinary functions:

let (|Chain|_|) f g data =
    f data |> Option.bind (fun r -> g data)

Then Chain <arg1> <arg2> <pat> is just calling the parameterized active pattern with two functions as an argument. When called, it binds the result to the pattern <pat>. In the above example, the two arguments are function values representing the patterns (these could be ordinary functions, but not lambda functions because of syntactic restrictions).

Tilburg answered 25/3, 2014 at 13:24 Comment(4)
Great information! Is it possible to partially apply them as well?Frederiksen
@Frederiksen You can partially apply them when calling them as functions, e.g. (|Chain|_|) f. When using them as patterns, you can only partially apply exhaustive patterns (because partial patterns return option).Tilburg
For example, given a pattern: let (|Add|) a b = a + bTilburg
You can actually write: match 1 with Add f -> f 10Tilburg

© 2022 - 2024 — McMap. All rights reserved.