Search code examples
f#active-pattern

How can I pass complex expression to parametrized active pattern?


I defined the active pattern "Expression" as follows:

let (|Expression|_|) expression _ = Some(expression)

Now I'm trying to use it in this way:

match () with
| Expression((totalWidth - wLeft - wRight) / (float model.Columns.Count - 0.5)) cw
    when cw <= wLeft * 4. && cw <= wRight * 4. ->
        cw
| Expression((totalWidth - wLeft) / (float model.Columns.Count - .25)) cw
    when cw <= wLeft * 4. && cw > wRight * 4. ->
        cw
| Expression((totalWidth - wRight) / (float model.Columns.Count - .25)) cw
    when cw > wLeft * 4. && cw <= wRight * 4. ->
        cw
| Expression(totalWidth / float model.Columns.Count) cw
    when cw > wLeft * 4. && cw > wRight * 4. ->
        cw
| _ -> System.InvalidProgramException() |> raise

But this results in "error FS0010: Unexpected symbol '-' in pattern". Is that fixable?

What am I trying to do is to write clearly a solution to the following equation:

max(wl - cw * .25, 0) + max(wr - cw * .25) + cw * columnCount = ActualWidth

where cw is the only variable.

Can you suggest any better way?


Solution

  • The langauge of expressions that can be used as arguments for parameterized active patterns is limited in some ways. As far as I can tell, the F# specification doesn't say that explicitly, but the grammar suggests that it must be possible to parse the argument expression as pat-param (page 90):

    pat-param :=
        | const
        | long-ident
        | [ pat-param ; ... ; pat-param ]
        | ( pat-param, ..., pat-param )
        | long-ident pat-param
        | pat-param : type
        | <@ expr @>
        | <@@ expr @@>
        | null

    So, I think you'll need to write your pattern matching differently. You could turn the expressions into ordinary arguments of the match construct and write something like this:

    match 
      (totalWidth - wLeft - wRight) / (float model.Columns.Count - 0.5),
      (totalWidth - wLeft) / (float model.Columns.Count - .25),
      (totalWidth - wRight) / (float model.Columns.Count - .25)
    with
    | cw1, _, _ when cw1 <= wLeft * 4. && cw1 <= wRight * 4. -> cw1
    | _, cw2, _ when cw2 <= wLeft * 4. && cw2 > wRight * 4. -> cw2
    | _, _, cw3 when cw3 > wLeft * 4. && cw3 <= wRight * 4. -> cw3
    | _ -> totalWidth / float model.Columns.Count
    

    If the pattern used in the expression is always the same, you could also use active pattern like:

    let (|Calculate|) w p _ =
      (totalWidth - w) / (float model.Columns.Count - p)
    

    ... and then write something like:

    let wDif = wLeft - wRight
    match () with
    | Calculate wDif 0.5 cw -> cw
    | Calculate wLeft 0.25 cw -> cw
    // .. etc.