Bound Routines

  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.

Bound Routine Example

  In the following example, we define a bound routine that takes an INT as an argument and returns an INT.
    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.

Binding some arguments

  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.

Leaving self unbound

  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.

Bound Routine Usage

  Just as is the case in C, there will be programmers who find bound routines indispensible and others who will hardly ever touch them. Since Sather's bound routines are strongly typed, much of the insecurity associated with function pointers (that I felt when using C, at least!) disappears.

Applicative Bound Routines

  They are generally useful when you want to write `` apply'' like routines in a container class, which will work on a collection of data items. A good set of useful bound routines may be found in the ARRAYT class, some examples of which are shown below. As usual, the elt! iter returns consecutive elements of the container.
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

Menu Structures

  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.

Higher Order Functions

    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

     


Benedict A. Gomes
Mon Apr 29 10:12:43 PDT 1996