octopusgrabbus
octopusgrabbus

Reputation: 10695

How to construct a match expression

I am allowing a command-line parameter like this --10GB, where -- and GB are constant, but a number like 1, 10, or 100 could be substituted in between the constant values, like --5GB.

I could easily parse the start and end of the string with substr or written a command line parser, but wanted to use match instead. I am just not sure how to structure the match expression.

let GB1     = cvt_bytes_to_gb(int64(DiskFreeLevels.GB1))
let arg = argv.[0]

let match_head = "--"
let match_tail  = "GB"

let parse_min_gb_arg arg =
    match arg with
    | match_head & match_tail -> cvt_gb_arg_to_int arg
    | _ -> volLib.GB1

I get a warning saying _ This rule will never be matched. How should the what is an AND expression be constructed?

Upvotes: 2

Views: 106

Answers (2)

Fyodor Soikin
Fyodor Soikin

Reputation: 80765

You can't match on strings, except matching on the whole value, e.g. match s with | "1" -> 1 | "2" -> 2 ...

Parsing beginning and end would be the most efficient way to do this, there is no need to get clever (this, by the way, is a universally true statement).

But if you really want to use pattern matching, it is definitely possible to do, but you'll have to make yourself some custom matchers (also known as "active patterns").

First, make a custom matcher that would parse out the "middle" part of the string surrounded by prefix and suffix:

let (|StrBetween|_|) starts ends (str: string) =
  if str.StartsWith starts && str.EndsWith ends then 
    Some (str.Substring(starts.Length, str.Length - ends.Length - starts.Length))
  else 
    None

Usage:

let x = match "abcd" with 
        | StrBetween "a" "d" s -> s
        | _ -> "nope"
// x = "bc"

Then make a custom matcher that would parse out an integer:

let (|Int|_|) (s: string) = 
    match System.Int32.TryParse s with 
    | true, i -> Some i 
    | _ -> None

Usage:

let x = match "15" with 
        | Int i -> i
        | _ -> 0
// x = 15

Now, combine the two:

let x = match "--10GB" with 
        | StrBetween "--" "GB" (Int i) -> i 
        | _ -> volLib.GB1
// x = 10

This ability of patterns to combine and nest is their primary power: you get to build a complicated pattern out of small, easily understandable pieces, and have the compiler match it to the input. That's basically why it's called "pattern matching". :-)

Upvotes: 5

Stuart
Stuart

Reputation: 5496

The best I can come up with is using a partial active pattern:

let (|GbFormat|_|) (x:string) =
    let prefix = "--"
    let suffix  = "GB"
    if x.StartsWith(prefix) && x.EndsWith(suffix) then
        let len = x.Length - prefix.Length - suffix.Length
        Some(x.Substring(prefix.Length, len))
    else
        None

let parse_min_gb_arg arg =
    match arg with
    | GbFormat gb -> gb
    | _ -> volLib.GB1

parse_min_gb_arg "--42GB"

Upvotes: 4

Related Questions