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; |