ceth
ceth

Reputation: 45295

Looking for something like `let` in `match` expression

Sometimes I use something like this:

match foo a with
| 1 -> printfn "%s" (foo a)
| 0 -> printfn "ok"

In this case I call foo function twice and if it is expensive call I use this code:

let tmp = foo a
match tmp with 
| 1 -> printfn "%s" tmp
| 0 -> printfn "ok"

But in this case I have created variable with outer scope (regarding match expression).

I am looking for something like this:

match (foo a) as tmp with
| 1 -> printfn "%s" tmp
| 0 -> printfn "ok

What do you use in this cases ? Is there any elegant solution ?

Update - real example:

let collection = getValuesFromDatabase a
match Array.length collection with 
| 0 -> printfn "nothing"
| _ -> bar collection.[0]

Upvotes: 0

Views: 95

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243051

In your real example, you can just use if. You are not really pattern matching on any complex data type which is where match shines. If you're testing whether a collection is empty, you can just write something like:

let collection = getValuesFromDatabase a
if Array.length collection = 0 then printfn "nothing"
else bar collection.[0]

Upvotes: 3

Fyodor Soikin
Fyodor Soikin

Reputation: 80744

Option 1: use a let- or do-block

let result =
    let tmp = foo a
    match tmp with
    | 1 -> printfn "%d" tmp
    | 0 -> printfn "ok"

Nesting the whole thing under a let-block keeps from polluting the namespace with tmp. The syntax is a bit heavy, but in return it allows for arbitrary complexity of the local computation.

Alternatively, if your result is a unit, you can replace let with do:

do
    let tmp = foo a
    match tmp with
    | 1 -> printfn "%d" tmp
    | 0 -> printfn "ok"

Option 2: use pattern aliasing

When pattern-matching, you can match a value with more than one pattern at once, separating the patterns with &, e.g.:

match [1;2;3] with
| (x::_)&(_::y::_) -> printfn "First element is %d, second element is %d" x y

Here, I am matching the same list with two patterns: x::_ and _::y::_. The example is a bit silly (I could have just matched with x::y::_), but it conveys the idea.

In your example, you can use this mechanism to capture the whole value by matching it with a trivial pattern:

 match foo a with
 | 1&x -> printfn "%d" x
 | 0 -> printfn "ok"

Update: the "real" example

This is in response to your edit, where you provided a "real" example, which deals with a collection.

This "real" example is actually different from the "toy" examples that you provided before, in that you want to capture collection, but you're matching on Array.length collection - not the same thing. In general, there is no shortcut for this, except putting it in a nested do or let block as described above. But in your specific case I could rewrite the match like this:

match getValuesFromDatabase a with 
| [||] -> printfn "nothing"
| xs -> bar xs.[0]

Here, instead of calling Array.length, I match the value with an empty array. This way, since I'm matching the collection itself, I can capture it in the second match case and use it to get the first element.

If you wanted to perform a more complex check than just the empty array check, you could also use a pattern guard:

match getValuesFromDatabase a with 
| xs when Array.length xs = 0 -> printfn "nothing"
| xs -> bar xs.[0]

Upvotes: 6

Related Questions