Pregunta

Is it possible in F# to pattern match directly against a let binding?

For example, this compiles without any warnings:

    let value = 
        match arg with
        | 1 -> "value1"
        | 2 -> "value2"
        | _ -> failwith "key not found"

Whereas the following gives the warning "This rule will never be matched" against the lines matching key2 and _:

    let key1 = 1
    let key2 = 2
    let value = 
        match arg with
        | key1 -> "value1"
        | key2 -> "value2"
        | _ -> failwith "key not found"

Is this because although they're immutable, the let bindings are unlike C# const variables?

¿Fue útil?

Solución

just use capital letters and [<Literal>] them and it works as expected.

let [<Literal>] X = 0
let [<Literal>] Y = 1
let bla arg =
    match arg with
    | X -> "zero"
    | Y -> "one"
    | somethingelse -> somethingelse.ToString()

the lower case name by convention typically means a wildcard that is bound to the name.

Otros consejos

The reason you're getting that error is because of what F# is doing when you use a variable name in the pattern clause of a match expression.

Let's say I have

match arg with
| x when x = 0 -> "zero"
| y when y = 1 -> "one"
| _ -> "other"

I think it's key here to note that, despite not defining x or y prior to the match, this code will still work. This is because x and y are just short codes which makes writing match expressions easier. Behind the scenes, the F# compiler is actually converting that x when x = 0 into "let binding" where x is bound to arg. x can then be used in the x = 0 expression and in the expression after the ->.

Going back to the problem you ran into:

let key1 = 1
let key2 = 2
let value = 
    match arg with
    | key1 -> "value1"
    | key2 -> "value2"
    | _ -> failwith "key not found"

The reason this won't work, is because in the match expression, F# is rebinding key1 to the value of arg, so key1 -> "value1" is equivalent toif arg1 = arg1 then "value1". The first pattern will always be matched; so, key2 and _ will never be reached.

I'm not sure how clear my explanation is, so I'll also throw in a second approach to explaining what's happened:

if you translate the match expression into an if-else it would look like this:

let key1 = 1
let key2 = 2

let value = 
    if let key1 = arg in arg = key1 then
        "value1"
    else if let key2 = arg in arg = key2 then
        "value2"
    else
        failwith "key not found"

(why yes, F# will let you throw let bindings into if expressions)

This if/else expression is equivalent to your match expression. In this form it becomes clear that the first condition will always evaluate to true.

I won't put it in here, but it may help to look at the code quotation of a match expression. I didn't really get what was going on with match expressions until I saw what the abstract syntax tree they generated looked like.

You can only use literals if you want to match against a particular value in a pattern matching case. An identifier means binding -- i.e. the actual value in this pattern matching case will be bound to the identifier that will be visible in the scope of this case.

As @DanielFabian has shown, you can define your own literals and give them names.

If the value you need to match against isn't known at compile time, you can use guards like so:

match arg with
| x when x = key1 -> "value1"
| x when x = key2 -> "value2"
| _ -> // etc

See the MSDN article on pattern matching for more details.

There are two main issues raised by the code you are trying to use.

Firstly pattern matching:

match arg with
| _ -> "value"

matches arg with anything and then returns "value"

match arg with
| a -> "value"

matches arg with anything, calls it "a", and then returns "value". You can think of the match having it's own little namespace, the a only exists in the match, and the match only 'sees' the things that have been named in it.

Secondly, if you want to match to a set of predefined values, then you probably want to use discriminated unions. You can define one like this:

type Keys=
| Key1
| Key2

Then match like this:

match arg with
| Key1 -> "value1"
| Key2 -> "value2"

In this case it matches against the type Keys, not a value called Key1 or Key2.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top