I often need to write programs that follow a simple pattern:
- For each of a list of objects (e.g. IP addresses, filesystems), try operation 1
- On the first one that succeeds, perform operation 2
- If none succeed, perform operation 3
There’s probably actually a name for this kind of thing, it seems like it would be common enough to warrant one. Let’s see how we can do this in OCaml. First set up some initial conditions. The function
success is operation 1,
square is operation 2 and
default is operation 3 in the preceeding list.
exception Result_found of int let success x = match x with |5 -> true |_ -> false in let square x = x * x in let mylist = [1; 2; 3; 4; 5; 6; 7; 8; 9] in let default = 10 in
First the most naive solution using a mutable variable. A variable is set to the default result, then all the items on the list are processed, if one succeeds then the variable is set to that, finally return whatever variable is set to at the end. This is a common method in shell script.
let d = ref default in List.iter (fun x -> if success x then d := square x else ()) mylist; print_endline (string_of_int !d);
The problems with this are obvious; firstly that it processes every list item regardless of whether it has already succeeded, and these may be time consuming or resource-intensive operations. Secondly it will return the last one, not the first. It is effectively just a
foreach loop despite being expressed in the OCaml-ish
List.iter syntax. So let us try something a bit better:
print_endline (string_of_int (try square (List.hd (List.filter success mylist)) with |Failure x -> default));
This does at least get the first one that succeeds with operation 1, however it still performs the test on each item. Let us see if we can combine these two approaches to return the first successful item without wasting any computation:
print_endline (string_of_int (try List.iter (fun x -> if success x then raise (Result_found x) else ()) mylist; default with |Result_found x -> square x));
This works by breaking out of the loop by raising an exception (there is no native
continue in OCaml tho’ it can be done by means of syntax extension). I don’t really like this approach; it is perfectly valid, and was considered the clever solution when I did Java way back when, but in my mind exceptions are a mechanism for dealing with conditions that are expected to happen only rarely and/or represent something going awry. And leaving the
default dangling there is messy.
Finally, I believe this is the idiomatic solution, using a recursive function to work through the list, performing operation 3 if the list is empty, and passing the untried list members back to itself after an unsuccessful operation 1. If operation 1 succeeds, operation 2 is performed and that is the result of the function.
(* default, sucess condition, function, list *) let rec try_each d c f xs = match xs with |  -> d (* reached the end without success, return default *) | x::xs -> match (c x) with | true -> f x | false -> try_each d c f xs in print_endline (string_of_int (try_each default success square mylist))
Unlike the others this also abstracts out the three operations, making it fully reusable. It looks tantalizingly close to a fold (and hence a one-liner), but I can’t quite make the leap from here to there…