0
votes

I have a function:

let map_plug (pairs : (char * char) list) input =
  let rec plug_aux pairs input = 
    match pairs with
    | [] -> 'A'
    | h :: t -> let first, second = h in
      match input with
      | first -> second
      | second -> first
      | _ -> plug_aux t input
  in plug_aux pairs input

...which takes a list of char pairs, and an input char.

The code I am concerned about is here:

let first, second = h in
      match input with
      | first -> second
      | second -> first
      | _ -> plug_aux t input

OCaml tells me that the first case is irrefutable, and that the other cases are not used. I find this behavior puzzling, as I would like to destructure a tuple and bind its elements, and match a variable with those elements; it doesn't seem to work.

I made a simpler example:

let x, y = (3, 4) in 
  match 4 with
  | x -> 7
  | y -> 8;;
Line 4, characters 2-3:
Warning 11: this match case is unused.

Testing this in Rust, too, also gives the same error. I realize at this point OCaml must believe that y is simply a shadowed name for a new binding, and thus is matching everything with it.

But I also know that using an if statement allows me to work with the bound elements of the destructured tuple, rather than declare any new variable. Is it possible to use a match statement to match the way I want it to?

3

3 Answers

1
votes

Patterns in OCaml are essentially structured constants where constituents of the constant can be specified as variable names. The names are bound to the corresponding part of the matched expression.

A match statement does not match a value against an arbitrary expression, but only a structured constant (in the above sense).

So this match statement:

match input with
| first -> ...

will always succeed, and will bind first the the same value as input. This is true whether or not there was a previous binding for the name first.

A match like this:

match input with
| first when first = fst -> code ()
| _ -> other_code ()

is essentially a complicated way of writing an if statement:

if input = fst then
    code ()
else
    other_code ()

In my opinion, the if statement is much clearer. For one thing, it doesn't introduce an irrelevant name like first.

0
votes

While investigating this I realized something a little unintuitive (at first) about scoping in this situation. A possible solution to my case:

let map_plug (pairs : (char * char) list) input =
  let rec plug_aux pairs input = 
    match pairs with
    | [] -> 'A'
    | h :: t -> let fst, snd = h in
      match input with
      | first when first = fst -> snd
      | second when second = snd -> fst
      | _ -> plug_aux t input
  in plug_aux pairs input

As you can see, I put pattern guards, essentially if statements, to produce a similar functionality that still used a match statement. This clued in to me that the scope of the pattern guards actually draws from bindings outside of the match statement, and whatever goes in the patterns themselves really doesn't matter, because I am trying to match against variables at runtime, not patterns.

In retrospect this is a really basic concept, but I was nonetheless surprised to have run into a problem here - in actuality, this would've been a form of dynamic pattern matching. A relevant SO post - Pattern matching a variable in OCaml? - talks about this.

0
votes

It sounds like you are confused about what Ocaml patterns do.

  • They can destructure data and check what variant of a sum type is used.

  • They can also compare data against literals.

But they cannot compare a variable with another, unless using when guards.

Basically a match is for giving name to things, not to check equality

Using examples :

match x with 
| 1 -> print_string "one" (* you can match x with 1 because 1 is a constant literal*)
| 2 -> print_string "two"
| y -> print_int y (* here y is a *new name* that is given x’s value*)
let y = 3 in 
match x with 
| 1 -> print_string "one" )
| 2 -> print_string "two"
| y -> print_int y (* here y is *still a new name* that is given x’s value. 
It shadows the previous y value ! 
It does not check that x = 3, and will always be executed when x is not 1 or 2*)

if you want to check in your match that x has the same value as y, you have to write something like that

let y = 3 in 
match x with 
| 1 -> print_string "one"
| 2 -> print_string "two"
| z when z = y -> print_string "three"
| z -> print_int z

the when guard allows you to add a condition to your match. That last condition would be more idiomatically written as :

let y = 3 in 
match x with 
| 1 -> print_string "one"
| 2 -> print_string "two"
| _ when x = y -> print_string "three"
| _ -> print_int x

since you don’t really need to rename x as z here.