Weird filter for function definition in Ocaml

Viewed 66

Sorry for this noob question but I have this function definition :

let f x = function
    | 0 -> 0
    | y -> 1;;

I understand that f is a function of x,y but why does it give :

  • 0 if y = 0
  • 1 if y = 1

Indeed according to the filter, should not f(0,1) give 0 since 0 -> 0 ?

3 Answers

This definition of a function is shorthand for

let g x y = match y with | 0 -> 0 | y -> 1;;

Its signature is: val g : 'a -> int -> int = <fun>

and you call both f or g like this:

g "hello" 5
- : int = 1

g "hello" 0
- : int = 0

So, the output of the function just depends on its second argument y.

If you do not need the name of the second argument (right hand side after ->) does not refer to it, you can also use wild cards:

let f x = function
    | 0 -> 0
    | _ -> 1

The different guards are being tested in order of how they were written. This is just the same in the Common Lisp construct

(defun f (x y)
  (cond
    ((= y 0) 0)
    (t 1)))

Where the last term of the cond form is the catch-all term.

The notation for functions of several arguments is a bit different in OCaml and in mathematics, which could be the cause of a little confusion here.

In OCaml the function notation makes it easy to partially apply functions. For instance we can write something like

# let add x y = x  + y;;
val add : int -> int -> int = <fun>
(* The addition of integers as we know it. *)

and specialise the first argument x to a 1, to define the successor operation:

# let successor = add 1;;
val successor : int -> int = <fun>
(* successor y is equivalent to add 1 x *) 

so that we can try

# successor 2;;
- : int = 3

Note that in OCaml the function add we defined above is a different function than the function

# let add' (x, y) = x + y;;
val add' : int * int -> int = <fun>

See how the signature differs. In mathematics, we usually do not need to emphasise the difference and hence identify the two functions. The add' function is, from OCaml perspective, a function of one argument, which is a pair.

In your analysis, you correctly state that f is a function of two arguments, x and y. It does not actually depend on the value of x, since the identifier x does not appear right to the equal sign in let f x = …. Instead for any value of x, f x returns the function defined by

function
| 0 -> 0
| y -> 1

So any of the expressions f (), f "whatever", f 0, f 7, f (0, 1) will evaluate to the function

function
| 0 -> 0
| y -> 1

(In these expressions x is bound to () or "whatever" or 0 or 7 or (0, 1) but that specific value does not participate to the actual computation of f x.)

We can try this out:

# let f x = function
    | 0 -> 0
    | y -> 1;;    
val f : 'a -> int -> int = <fun>
# f ();;
- : int -> int = <fun>
# f "whatever";;
- : int -> int = <fun>
# f 0;;
- : int -> int = <fun>
# f 7;;
- : int -> int = <fun>
# f (0, 1);;
- : int -> int = <fun>

If we add a second argument, this will actually return a 0 or a 1 according to that function definition:

# f () 0;;
- : int = 0
# f () 11;;
- : int = 1
# f (0,1) 0;;
- : int = 0
# f (0,1) 11;;
- : int = 1
(* etc. *)

We can also use parenthesis to emphasise how OCaml is actually reading these expressions

# (f ()) 0;;
- : int = 0
# (f (0,1)) 11;;
- : int = 1

The function syntax defines an anonymous function taking a single argument, and pattern matches on that argument. There are any number of places in OCaml where this can be useful. Given that OCaml is a highly pragmatic programming language, its inclusion makes sense.

The naming of the patterns doesn't particularly matter, except that you want to be careful not to shadow other values already bound to the same name.

Consider:

let f x = 
  function
  | 0 -> 0
  | x = x + x

If we evaluate f 4 5 we get 10 rather than 9 because x now is bound to the value 5 which we passed in. By using a different name in the pattern, we avoid this.

let f x = 
  function
  | 0 -> 0
  | y = x + y

Now, in this case it would have been far more straightforward to just write:

let f x y = 
  match y with
  | 0 -> 0
  | y = x + y

Or:

let f x y = 
  if y = 0 then 0
  else x + y

But let's say we wanted to pass an anonymous function to List.map to map a list of integers to new values, where 1, 2, or 3 are 0 and anything else is multiplied by two:

let lst = [4; 8; 1; 9; 3] in
List.map (fun x -> match x with 1 | 2 | 3 -> 0 | _ -> x * 2) lst

The function syntax cleans this up a bit:

let lst = [4; 8; 1; 9; 3] in
List.map (function 1 | 2 | 3 -> 0 | x -> x * 2) lst

Or:

let lst = [4; 8; 1; 9; 3] in
let f = function 1 | 2 | 3 -> 0 | x -> x * 2 in
List.map f lst
Related