I’ve been exploring closures and I found them to be very accommodating but I also found they can obfuscate the code if you rely on them too much.
Here’s an example I’ve been working on that embraces closures. It provides a ‘cool’ solution to a tricky problem but it also makes it very hard to understand the intent of the code.
The function dropElement takes a list and function to match an element in the the list and returns a type of ('a * 'a list) option. If the function(dropElement) finds a match in the list it will return that element and the list minus the found element.
Example:
given a list [1;2;3;4;5;6;7;8;9;10;]
and the match condition element = 4
the returned value will be Some(4, [1;2;3;5;6;7;8;9;10])
let dropElement matchElem lst =
let rec dropElemAux lst func =
match lst with
| [] -> None
| hd::tl ->
if (matchElem hd)
then
Some(hd, (func tl))
else
dropElemAux tl (fun t -> func (hd::t)) in
dropElemAux lst (fun d -> d)
let ans = dropElement ((=)4) [1;2;3;4;5;6;7;8;9;10;]
let () =
match ans with
| None -> print_endline "No element matched"
| Some (elem, lst) ->
print_endline(string_of_int elem);
print_newline();
List.iter (fun x -> print_endline(string_of_int x)) lst
Now the above code works but I find it very confusing and I wrote it.
Well, in that particular case, you happen (heh) to have reinvented difference lists, so you can abstract that!
module Difflist = struct
type 'a t = 'a list -> 'a list
let empty x = x
let cons hd (tl : _ t) : _ t = fun x -> tl (hd::x)
let append (t1 : _ t) (t2 : _ t) : _ t = fun x -> t1 (t2 x)
let retAppend (t1 : 'a t) t2 = t1 t2
end
let dropElement matchElem lst =
let rec dropElemAux rest acc =
match rest with
| [] -> None
| hd::tl ->
if matchElem hd then
Some(hd, Difflist.retAppend acc tl)
else
dropElemAux tl (Difflist.cons hd acc)
in
dropElemAux lst Difflist.empty
Personally, I would have gone for the the simpler:
let dropElement matchElem lst =
let rec dropElemAux rest acc =
match rest with
| [] -> None
| hd::tl ->
if matchElem hd then
Some(hd, List.rev_append acc tl)
else
dropElemAux tl (hd :: acc)
in
dropElemAux lst []
In general, there is no one answer to your question. If some closures are parts of some data-structure, like here, you can always abstract them and hide the internal details.
Just for completeness: you might want to look at List.filter, which solves a similar problem but would remove all matching elements, not just the first.
To answer your title question “when to use closures” your examples gives some guidance to that. The closure “matchElem” could hide anything – heck, a primality-checker, right? (“remove and return the first prime number”) This is an excellent use of a closure. OTOH, “func” is basically just a wrapper for an accumulator of elements that sets up what amounts to a “rev_append” (as Drup points out).
So one maxim about closures could be: take a closure argument when you want to allow a caller to provide arbitrary logic. don’t take one, when you can clearly see the logic that the closure encapsulates, and it’s obvious how to turn it into a data-structure.
Drup’s rev_append example has the extra nicety that it’s both obviously tail-recursive, using “accumulating parameters”. This “accumulating parameters” stuff is really valuable to understand – it’s connected to continuations via Reynolds’ “defunctionalization” (his “Definitional Interpreters …” paper is a classic) but even without understanding all that, just knowing how to use accumulating parameters to turn stack-recursion into tail-recursion is very important.