views:

314

answers:

4

I have a function of the form

'a -> ('a * int) list -> int

let rec getValue identifier bindings = 
  match bindings with
  | (identifier, value)::tail -> value
  | (_, _)::tail -> getValue identifier tail
  | [] -> -1

I can tell that identifier is not being bound the way I would like it to and is acting as a variable within the match expression. How to I get identifier to be what is passed into the function?

Ok! I fixed it with a pattern guard ie | (i, value)::tail when i = indentifier -> value but I find this ugly compared to the way I originally wanted to do it (I'm only using these languages because they are pretty...). Any thoughts?

+1  A: 

This is a common complaint, but I don't think that there's a good workaround in general; a pattern guard is usually the best compromise. In certain specific cases there are alternatives, though, such as marking literals with the [<Literal>] attribute in F# so that they can be matched against.

kvb
+4  A: 

This is not directly an answer to the question: how to pattern-match the value of a variable. But it's not completely unrelated either.

If you want to see how powerful pattern-matching could be in a ML-like language similar to F# or OCaml, take a look at Moca.

You can also take a look at the code generated by Moca :) (not that there's anything wrong with the compiler doing a lot of things for you in your back. In some cases, it's desirable, even, but many programmers like to feel they know what the operations they are writing will cost).

Pascal Cuoq
+1: cool! Looks like an interesting choice for implementing DSLs.
Juliet
+7  A: 

You can use F# active patterns to create a pattern that will do exactly what you need. F# supports parameterized active patterns that take the value that you're matching, but also take an additional parameter.

Here is a pretty stupid example that fails when the value is zero and otherwise succeeds and returns the addition of the value and the specified parameter:

let (|Test|_|) arg value = 
  if value = 0 then None else Some(value + arg)

You can specify the parameter in pattern matching like this:

match 1 with
| Test 100 res -> res // 'res' will be 101

Now, we can easily define an active pattern that will compare the matched value with the input argument of the active pattern. The active pattern returns unit option, which means that it doesn't bind any new value (in the example above, it returned some value that we assigned to a symbol res):

let (|Equals|_|) arg x = 
  if (arg = x) then Some() else None

let foo x y = 
  match x with
  | Equals y -> "equal"
  | _ -> "not equal"

You can use this as a nested pattern, so you should be able to rewrite your example using the Equals active pattern.

Tomas Petricek
Interesting (the writing of the active patterns for a type must feel like boilerplate, but you only write them once and take advantage of them many times). How do they show up in a module signature?
Pascal Cuoq
The `Equals` active pattern is generic and works for any type that supports comparison (a special constraint on type variables available in F#). Active patterns show as a functions of a special name in the signature. The signature looks like this: `val ( |Equals|_| ) : 'a -> 'a -> unit option when 'a : equality`
Tomas Petricek
+3  A: 

One of the beauties of functional languages is higher order functions. Using those functions we take the recursion out and just focus on what you really want to do. Which is to get the value of the first tuple that matches your identifier otherwise return -1:

let getValue identifier list = 
match List.tryFind (fun (x,y) -> x = identifier) list with
    | None      -> -1
    | Some(x,y) -> y

//val getValue : 'a -> (('a * int) list -> int) when 'a : equality

This paper by Graham Hutton is a great introduction to what you can do with higher order functions.

Cesar Mendoza
*eyes explode* Point-free style is clever, but unreadable and unmaintainable. `fun x -> x |> List.map fst |> List.filter (fun y -> y = identifier)` expresses the same thing without sacrificing readability.
Juliet
Point taken. I hope this revision is better. :)
Cesar Mendoza
True, tryFind would be the best use of the language since I was only temporarily using -1 as a stand-in for None. But I'm glad I learned a little more about pattern matching and variable binding.
Sandy Vanderbleek
In that case the function getValue would look something like this:let getValue identifier = List.tryFind (fun (x,y) -> x = identifier) >> Option.map snd//val getValue : 'a -> (('a * 'b) list -> 'b option) when 'a : equality
Cesar Mendoza