Bound routines are Sather's equivalent of function pointers. Like everything else in Sather, they must be strongly typed. The type of a bound routine most closely represents the type ofa parametrized class, with the possible addition of a return type.
br: ROUT{INT}:INT := #ROUT(1.plus(_)); #OUT+br.call(9);
The variable br is typed as a bound routine which takes an integer as argument and returns an integer. The routine 1.plus, which is of the appropriate type, is then assigned to br. The routine associated with br may then be invoked by the built in function call. Just as we would when calling the routine INT::plus(INT), we must supply the integer argument to the bound routine.
We could also define the same ``plus one'' routine ourselves
class INCREMENTOR is create: SAME is return(new) end; increment(a: INT): INT is return(a+1) end; end; main is a ::= #INCREMENTOR; br: ROUT{INT}:INT := #ROUT(a.increment(_)); result_of_calling_br: INT := br.call(9); #OUT+result_of_calling_br; -- Should be 10 end;The variable br is typed as a bound routine which takes an integer as argument and returns an integer. The routine increment, which is of the appropriate type, is then assigned to br. The routine associated with br may then be invoked by the built in function call. Just as we would when calling the routine a.increment, we must supply the integer argument to the bound routine.
result_of_calling_br: INT := br.call(9)is equivalent to:
result_of_calling_br:INT := foo(9);which returns the value 14.
When a bound routine is created, it can preset some of the values of the arguments. For example:
class ADDER is create: SAME is return(new) end; -- A trivial create routine - just returns a new instance. add(a: INT,b: INT): INT is return(a+b) end; end; class TEST_BR is main is adder ::= #ADDER; -- Call the create routine of ADDER -- No preset arguments br1: ROUT{INT,INT}: INT := #ROUT(adder.add(_,_)); br1_res: INT := br1.call(11,15); -- Preset the first argument of foo to 53 br2: ROUT{INT}:INT := #ROUT(adder.add(53,_)); br2_res: INT := br2.call(9); #OUT+br1_res+","+br2_res; -- Should yield 26 and 62 end; end;In the example above, br2 binds the first argument of foo to 53 and the second argument is left unbound. This second argument will have to be supplied by the caller of the bound routine. br1 binds neither argument and hence when it is called, it must supply both arguments. The result of calling br1 should be 26 and the result of calling br2 should be 62.
Bound routines are often used to apply a function to arbitrary objects of a particular class. For this usage, we need the self argument to be unbound. In the following example we will make use of the plus routine from the INT class.
... from the INT class plus(arg: INT): INT is ... definition of plus end; main is -- Leaving self and the argument unbound plusbr1: ROUT{INT,INT}:INT := #ROUT(_:INT.plus(_)); br1res: INT := plusbr1.call(9,10); -- 19 -- Binding self, but leaving the argument unbound plusbr2: ROUT{INT}:INT := #ROUT(3.plus(_)); br2res: INT := plusbr2.call(15); -- 18 -- Binding the argument but leaving self unbound plusbr3: ROUT{INT}:INT := #ROUT(_.plus(9)); br3res: INT := plusbr3.call(11); -- 20 #OUT+br1res+","+br2res+","+br3res; -- 19,18,20 end;
In the above example, plusbr1 leaves both self and the argument to plus unbound. Note that we must specify the type of self when creating the bound routine, otherwise the compiler cannot know which class the routine belongs to (the type could also be an abstract type that defines that feature in its interface). plusbr2 binds self to 3, so that the only argument that need be supplied at call time is the argument to the plus. plusbr3 binds the argument of plus to 15, so that the only argument that need be supplied at call time is self for the routine.
from ARRAY{T} ... some(test:ROUT{T}:BOOL):BOOL is -- True if some element of self satisfies `test'. -- Self may be void. loop if test.call(elt!) then return true end end; return false end; every(test:ROUT{T}:BOOL):BOOL is -- True if every element of self satisfies `test'. -- Self may be void. loop if ~test.call(elt!) then return false end end; return true end;These routines may be used thus:
class MAIN is main is a ::= #ARRAY(|0.0,1.0,3.0,5.0|); br ::= #ROUT(gt_four); #OUT+a.every(br); -- Returns false, all elements are not -- greater than four #OUT+a.some(br); -- Returns true, one element is > 4.0 end; gt_four(arg: FLT): BOOL is return(arg > 4.0) end; -- Return true if the argument is greater than four
Another, common use of function pointers is in the construction of a set of choices. They may be used to, for instance, create a MENU class which associates various menu entries with bound routines. (This corresponds to the COMMAND pattern from the gang-of-four text called Design Patterns).
class MENU is private attr menu_choices: FMAP{STR,ROUT}; -- Hash table mapping from strings to bound routines create: SAME is return(new) end; add_menu_item(name: STR, function: ROUT) is -- Add a menu item to the hash table, indexed by it's name menu_choices := menu_choices.insert(name,function); end; private user_selects(m: STR) is -- Perform an action when the user selects a particular menu item -- Look up the bound routine in the hash table, and call it. routine: ROUT := menu_choices.get(m); routine.call; end; run is -- In a loop, get user input, if it is not the word "done" -- then call the user_selects routine. loop #OUT+">"; val ::= IN::get_line.str; if (val = "done") then break! else user_selects(val); end; end; end; end; class MAIN is main is m: MENU := #MENU; m.add_menu_item("hello",#ROUT(print_hello)); m.add_menu_item("why",#ROUT(print_why)); m.run; end; print_hello is #OUT+"Hello there yourself!\n" end; print_why is #OUT+"My existance is a cosmic mystery\n" end; end;This generates the following session:
ttyp0 icsib78:~/tmp>a.out >hello Hello there yourself! >why My existance is a cosmic mystery >hello Hello there yourself! >done ttyp0 icsib78:~/tmp>
A very similar usage may be found in the GET_OPT class which may be found in Contrib/gomes/get_opt.sa of the sather distribution. GET_OPT is capable of parsing a set of command line options in the standard unix form -<keyword1> text -<keyword2> text2. The class associates a bound routine with each keyword, and calls that bound routine with ``text'' as its argument. If the bound routine expects an argument of a different type (one of INT, FLT or BOOL), it tries to convert the string to a value of the appropriate type.
Bound routines may also be composed to form higher order functions. Our example takes two bound routine predicates (bound routines that return a boolean value) as arguments and returns another bound routine which is the result of ORing the results of the two predicates.
class MY_TEST is make_or(bp1: ROUT{INT}:BOOL, bp2: ROUT{INT}:BOOL): ROUT{INT}:BOOL is -- bp1 and bp2 are bound routine predicates that take INTs as -- arguments and return BOOLs -- make_or returns a bound routine which can compute -- the ``or'' of ``bp1'' and ``bp2'' applied to the same argument res: ROUT{INT}: BOOL; res := #ROUT(stub_for_or(_,bp1,bp2)); return res; end; stub_for_or(arg: INT, bp1,bp2: ROUT{INT}:BOOL): BOOL is -- Stub routine that will be bound - it applies bp1 and bp2 to the -- argument ``arg'' and returns the ``or'' of the two results bp1_res: BOOL := bp1.call(arg); bp2_res: BOOL := bp2.call(arg); return(bp1_res or bp2_res); end; main is gt4: ROUT{INT}:BOOL := #ROUT(_:INT.gt(4)); -- True if the arg is > 4 lt2: ROUT{INT}:BOOL := #ROUT(_:INT.lt(2)); -- True if the arg is < 2 -- Create the composite predicate i.e. ``gt4'' or ``lt2'' gt4_or_lt2: ROUT{INT}:BOOL := make_or(gt4,lt2)); #OUT+gt4.call(5); -- Output is "true" #OUT+gt4_or_lt2(1)+"\n"; -- "true" #OUT+gt4_or_lt2(5)+"\n"; -- "true" #OUT+gt4_or_lt2(3)+"\n"; -- "false" end; end;
Using bound routines, we have created a test for whether a number is either greater than 4 or less than two