Just as is the case with C function pointers, there will be programmers who find closures indispensible and others who will hardly ever touch them. Since Sather's closures are strongly typed, much of the insecurity associated with function pointers in C disappears.
Closures are useful when you want to write Lisp-like "apply" routines in a class which contains other data . Routines that use routine closures in this way may be found in the class ARRAY{T}. Some examples of which are shown below.
every(test:ROUT{T}:BOOL):BOOL is
-- True if every element of self satisfies 'test'.
loop
e ::= elt!; -- Iterate through the array elements
if ~test.call(e) then
return false;
end
-- If e fails the test, return false immediately
end;
return true
end; |
The following routine which takes a routine closure as an argument and uses it to select an element from a list
select(e:ARRAY{INT}, r:ROUT{INT}:BOOL):INT is
-- Return the index of the first element in the array 'e' that
-- satisfies the predicate 'r'.
-- Return -1 if no element of 'e' satisfies the predicate.
loop i:INT := e.ind!;
if r.call(e[i]) then
return i;
end;
end;
return -1;
end;
|
The selection routine may be used as shown below:
a:ARRAY{INT} := |1,2,3,7|;
br:ROUT{INT}:BOOL := bind(_.is_eq(3));
#OUT + select(a,br); -- Prints the index of the first element of 'a'
-- that is equal to '3'. The index printed is '2' |
Another common use of function pointers is in the construction of an abstraction for a set of choices. The MENU class shown below maintains a mapping between strings and routine closures associated with the strings.
class MENU is
private attr menu_actions:MAP{STR,ROUT};
-- Hash table from strings to closures
private attr default_action:ROUT{STR};
create(default_act:ROUT{STR}):SAME is
res:SAME := new;
res.menu_actions := #MAP{STR,ROUT};
res.default_action := default_act;
return(res)
end;
add_item(name:STR, func:ROUT) is menu_actions[name] := func end;
-- Add a menu item to the hash table, indexed by 'name'
run is
loop
#OUT + ">";
command: STR := IN::get_str; -- Gets the next line of input
if command = "done" then
break!;
elsif menu_actions.has_ind(command) then
menu_actions[command].call;
else
default_action.call(command);
end;
end;
end;
end; |
We use this opportunity to create a textual interface for the calculator described earlier (See unnamedlink):
class CALCULATOR is
private attr stack:A_STACK{INT};
private attr menu:MENU;
create:SAME is
res ::= new;
res.init;
return res;
end;
private init is -- Initialize the calculator attributes
stack := #;
menu := #MENU(bind(push(_)));
menu.add_menu_item("add",bind(add));
menu.add_menu_item("times",bind(times));
end;
run is menu.run; end;
...
--- Now, the main routines of the calculator computation are:
push(s:STR) is
-- Convert the value 's' into an INT and push it onto the stack
-- Do nothing if the string is not a valid integer
c: STR_CURSOR := s.cursor;
i: INT := c.int;
if c.has_error then
#ERR + "Bad integer value:" + s;
else
stack.push(i);
end;
end;
add is -- Add the two top stack values and push/print the result
sum:INT := stack.pop + stack.pop;
#OUT + sum+"\n";
stack.push(sum);
end;
times is -- Multiply the top stack values and push/print the result
product:INT := stack.pop * stack.pop;
#OUT + product + "\n";
stack.push(product);
end;
end; -- class CALCULATOR |
This calculator can be started by a simple main routine
class MAIN is
main is
c:CALCULATOR := #;
c.run;
end;
end; |
:
After compiling the program, we can then run the resulting executable
prompt> a.out >3 >4 >add 7 >10 >11 >times 110 >done prompt> |
An iterator closure is created that may be used to extract elements of a map that satisfy the selection criteria defined by 'select'.
select: ROUT{T}:BOOL;
select_elt: ITER{MAP{E,T}}:T;
...
select_elt := bind(_.filter!(select)); |
This creates an iterator closure that returns successive odd integers, and then prints the first ten:
odd_ints: ITER{INT}:INT := bind(1.step!(_,2));
loop
#OUT + odd_ints.call!(10);
end; |