Let's look at how you can print the elements in a list using fold_left.
let print lst =
List.fold_left
(fun i x -> Format.printf "%d: %d\n" i x; i + 1)
0 lst;
The initial index of 0 is passed in as the initial value. Each time through we have the side effect of printing a line, and then update the initial value to i + 1.
Result:
utop # print [1;4;6;2;7];;
0: 1
1: 4
2: 6
3: 2
4: 7
- : int = 5
This shows us nicely how the fold updates its accumulator as it iterates through a list.
But if we're going to search for a value at an index, let's use a tuple of a current index and an option type for the return value, in case we don't find the index we're looking for.
let at idx lst =
List.fold_left
(fun i x ->
match i with
| (_, Some _). -> i
| (idx', _) when idx = idx' -> (idx', Some x)
| (idx', _) -> (idx' + 1, None))
(0, None)
lst
We start with 0 and None as our initial value. For each item in the list we check to see if the option type value in the accumulator is Some _. We don't care about the value, but if it's not None we've found what we're looking for and do not need to update the accumulator.
If the accumulator does contain None and the current index is the same as the one we're looking for, update the accumulator with Some of the current value. The index does not need to be updated.
If the index is not the one we're looking for, increment the index and continue.
utop # at 3 [1;2;3;4;5];;
- : int * int option = (3, Some 4)
But since we don't need the first element in the tuple, we can use a let binding to name the result we are looking for and return that.
let at idx lst =
let (_, result) = List.fold_left
(fun i x ->
match i with
| (_, Some _) -> i
| (idx', _) when idx = idx' -> (idx', Some x)
| (idx', _) -> (idx' + 1, None))
(0, None)
lst
in
result
Now:
utop # at 3 [1;2;3;4;5];;
- : int option = Some 4
Taking a look at how List.fold_left can be implemented may be instructive.
let rec fold_left f init lst =
match lst with
| [] -> init
| x::xs -> fold_left f (f init x) xs
In at the Some _ condition in the accumulator tuple causes that accumulator value to propagate to the end of the fold without update.
Alternately we could use a local exception to break out of the fold immediately on finding the index we're looking for.
let at idx lst =
let exception Early_exit of int in
try
let (_, result) = List.fold_left
(fun i x ->
match i with
| (_, Some _) -> i
| (idx', _) when idx = idx' -> raise (Early_exit x)
| (idx', _) -> (idx' + 1, None))
(0, None)
lst
in
result
with Early_exit x -> Some x
Or perhaps a little bit more cleanly:
let at idx lst =
let exception Early_exit of int in
match List.fold_left
(fun i x ->
match i with
| (_, Some _) -> i
| (idx', _) when idx = idx' -> raise (Early_exit x)
| (idx', _) -> (idx' + 1, None))
(0, None)
lst with
| (_, result) -> result
| exception Early_exit x -> Some x
Of course, we now know that if the exception Early_exit wasn't raised, the index wasn't found, so the result would always be None.
let at idx lst =
let exception Early_exit of int in
match List.fold_left
(fun i x ->
match i with
| (_, Some _) -> i
| (idx', _) when idx = idx' -> raise (Early_exit x)
| (idx', _) -> (idx' + 1, None))
(0, None)
lst with
| _ -> None
| exception Early_exit x -> Some x
And testing:
utop # at 3 [1;2;3;4;5];;
- : int option = Some 4
But if we're using exceptions this way, we really don't need List.fold_left at all. We can just use List.iteri.
let at idx lst =
let exception Found of int in
try
List.iteri (fun i x -> if i = idx then raise (Found x)) lst;
None
with Found x -> Some x