Yet another take on this problem.
let first_last xs =
let rec last_non_empty = function
| [x] -> x
| _ :: xs' -> last_non_empty xs'
| [] -> failwith "first_last: impossible case!"
in
match xs with
| [] -> failwith "first_last"
| x::_ -> (x, last_non_empty xs)
Some properties of this implementation:
(1) it meets the specification 'a list -> 'a * 'a
:
utop > #typeof "first_last";;
val first_last : 'a list -> 'a * 'a
(2) it works for singleton lists: first_last [x] = (x,x)
:
utop> first_last [1];;
- : int * int = (1, 1) utop> first_last ["str"];;
- : bytes * bytes = ("str", "str")
(3) it's tail-recursive (hence it won't cause stack overflow for sufficiently big lists):
utop > first_last (Array.to_list (Array.init 1000000 (fun x -> x+1)));;
- : int * int = (1, 1000000)
(4) it traverses the input list one time only; (5) it avoids creating new lists as it goes down the recursive ladder; (6) it avoids polluting the namespace (with the price of not allowing the reuse of a function like last
).
And another rather simple variant, from the first principles (I was trying to illustrate "wishful thinking" in the spirit of the SICP book):
(* Not tail-recursive, might result in stack overflow *)
let rec first_last = function
| [] -> failwith "first_last"
| [x] -> (x,x)
| x :: xs -> (x, snd (first_last xs))