HOME Try Sather download Post Messages

11. Procedures as data


1. Introduction

In Sather, you can treat procedures as data, which means that you can give procedures as arguments of methods and that you can make methods that return a procedure. Let me call such classes as function classes here. A function class can be a value of the MAP type.

Two types of function classes are available: one is ROUT class which is a typical procedure and the other is ITER, a iterator.

2. How to use the function classes

I will show a simple example.
01:     -- rout.sa
02:     -- simple examples for ROUT and ITER
03:     
04:     class MAIN is
05:     
06:     -- helper function to treat TUP{INT,INT}
07:        aux(tu:TUP{INT, INT}):INT is
08:           return tu.t1 + tu.t2;
09:        end;
10:     
11:     -- print function
12:        print(obj:$STR, sep:STR) is
13:           #OUT + obj + sep;
14:        end;
15:     
16:        
17:        main is
18:     -- declaring instances of function classes
19:           csqrt:ROUT{FLT}:FLT;
20:           cpow:ROUT{FLT,FLT}:FLT;
21:           ccomp:ROUT{TUP{INT,INT}}:INT;
22:           istep:ITER{once INT,once INT,once INT}:INT;
23:     
24:     -- assigning the function class instances
25:           csqrt:=bind(_.sqrt);
26:           cpow:=bind(_.pow(_));
27:           ccomp:=bind(aux(_));
28:           istep:=bind(_.step!(_,_));
29:     
30:     -- calling the function class instances
31:           print(csqrt.call(2.0), "\n");                      -- 1.414....
32:           print(cpow.call(0.99999, 300.0), "\n");            -- 0.997
33:           print(ccomp.call(#TUP{INT,INT}(1,2)), "\n");       -- 3
34:           loop
35:     	 print(istep.call!(1,5,3), " ");                 -- 1 4 7 10 13
36:           end;
37:           #OUT + "\n";
38:        end;
39:     end;
Line Explanation
19 — 22 declaring to use function classes with arguments and type of the return value.
25 — 28 Method bind is to assign a procedure. '_' represents an argument. As a complicate procedures cannot be the argument of the bind, you should define the procedure as a single method and give it to the bind.
31 — 37 Use call and call! methods to call ROUT and ITER type function classes, respectively.

In the above code, I declared the instances of function classes and then assigned them separetely just for explanation. You can do them simultaneously, however, like:

cpow:ROUT{FLT,FLT}:FLT:=bind(_.pow(_));
$ sacomp rout.sa -o rout
$ ./rout
1.41421
0.997
3
1 4 7 10 13 

3. Methods that take procedures as arguments

Most of parameterized classes have methods that take procedures as arguments. For instance, the ARRAY class has methods for filtering, mapping, folding, and sorting.
01:     -- horder.sa
02:     -- examples for higher order functions for ARRAY{INT}
03:     
04:     class MAIN is
05:        const iarr:ARRAY{INT}:=|0,9,2,7,3,4,1,5,8,6|; 
06:        
07:     
08:     -- print function
09:        print(obj:$STR, sep:STR) is
10:           #OUT + obj + sep;
11:        end;
12:     
13:     
14:     -- filtering
15:        select_even:ARRAY{INT} is
16:           return iarr.remove_if(bind(_.is_odd));
17:        end;
18:     
19:     -- mapping
20:        sq:ARRAY{INT} is
21:           a:ARRAY{INT};
22:           a:=iarr.copy;
23:           a.map(bind(_.pow(2)));
24:           return a;
25:        end;
26:     
27:     -- folding
28:        sum:INT is
29:           return iarr.reduce(bind(_.plus(_)));
30:        end;
31:     
32:     -- a helper function for order_by_desc
33:        gt(i,j:INT):BOOL is
34:           return i > j;
35:        end;
36:     
37:     -- sorting
38:        order_by_desc:ARRAY{INT} is
39:           a:ARRAY{INT};
40:           a:=iarr.copy;
41:           a.insertion_sort_by(bind(gt(_,_)));
42:           return a;
43:        end;
44:     
45:     
46:        main(av: ARRAY{STR}) is
47:           print(select_even, "\n");
48:           print(sq, "\n");
49:           print(sum, "\n");
50:           print(order_by_desc, "\n");
51:        end;
52:     end;
$ sacomp horder.sa -o horder
$ ./horder
{0,2,4,8,6}
{0,81,4,49,9,16,1,25,64,36}
45
{9,8,7,6,5,4,3,2,1,0}

4. A practical (?) application: improved reverse polish calculator

Following code shows the revised version of the reverse polish calculator presented in
the previous chapter. I use the ROUT class in it.
The code get smarter by using MAP{STR, ROUT} classes and defining OPERATORS class.
01:     -- rpcalc.sa: a simple reverse polish calculator, second version
02:     
03:     class OPERATORS is
04:     
05:        attr biop:MAP{STR,ROUT{FLTD,FLTD}:FLTD};  -- MAP of binary operators
06:        attr unop:MAP{STR,ROUT{FLTD}:FLTD};      -- unary operators
07:     
08:        create:SAME is
09:           return new.init;
10:        end;
11:     
12:        init:SAME is
13:           init_biop;
14:           init_unop;
15:           return self;
16:        end;
17:     
18:        init_biop is          -- adding binary operators to the hash table
19:           biop:=#;
20:           biop["+"]:=bind(_.plus(_));
21:           biop["-"]:=bind(_.minus(_));
22:           biop["*"]:=bind(_.times(_));
23:           biop["/"]:=bind(_.div(_));
24:           biop["^"]:=bind(_.pow(_));
25:        end;
26:     
27:        init_unop is -- adding unary operators to the hash table
28:           unop:=#;
29:           unop["exp"] := bind(_.exp);
30:           unop["log"] := bind(_.log);
31:           unop["sin"] := bind(_.sin);
32:           unop["cos"] := bind(_.cos);
33:           unop["tan"] := bind(_.tan);
34:           unop["asin"]:= bind(_.asin);
35:           unop["acos"]:= bind(_.acos);
36:           unop["atan"]:= bind(_.atan);
37:        end;
38:     
39:        is_operator(s:STR):BOOL is      -- is it an operatorˇ©
40:           return is_biop(s) or is_unop(s);
41:        end;
42:     
43:        is_biop(s:STR):BOOL is          -- is it binaryˇ©
44:           return biop.has_ind(s);
45:        end;
46:     
47:        is_unop(s:STR):BOOL is          -- unaryˇ©
48:           return unop.has_ind(s);
49:        end;
50:     
51:        apply(s:STR, a:FLTD):FLTD is    -- calculating with a unary operator
52:           return unop[s].call(a);      -- use it like apply("log", 3.3)
53:        end;
54:     
55:        apply(s:STR, a,b:FLTD):FLTD is  -- calculating with a binary operator
56:           return biop[s].call(a,b);    -- use it like apply("+", 1.1, 2.3)
57:        end;                            -- you can override methods if they have different arguments.
58:     
59:     end;  --OPERATORS
60:     
61:     
62:     
63:     class MAIN is
64:     
65:        const re:REGEXP:=REGEXP::regexp("^[+\\-]?[0-9]+\\.?[0-9]*$", false);  -- a regular expression to see if the string represents a real number.
66:        attr stack:STACK{FLTD};
67:        attr op:OPERATORS;
68:     
69:        is_number(word:STR):BOOL is  -- is it a real numberˇ©
70:           return re.match(word);
71:        end;
72:     
73:        bicalc(word:STR) is          -- using a binary operator
74:           f1,f2 :FLTD;              -- pop twice, calculate, then push the result
75:           f2:=stack.pop;
76:           f1:=stack.pop;
77:           stack.push(op.apply(word,f1,f2));
78:        end;
79:     
80:        push_to_stack(word:STR) is           -- pushing a real number on to the stack
81:           stack.push(word.cursor.get_fltd);      
82:        end;
83:     
84:        clear_stack is                      -- clearing the stack
85:           a:FLTD;
86:           loop
87:     	 while!(~stack.is_empty);
88:     	 a:=stack.pop;
89:           end;
90:        end;
91:     
92:        show_stack:STR is                    -- converting the stack to a string
93:           s:STR:="> ";
94:           loop
95:     	 s:= s + stack.reverse_elt!.str + " ";
96:           end;
97:           return s;
98:        end;
99:     
100:        main(av: ARRAY{STR}) is
101:           line, word:STR;
102:           stack:=#;
103:           op:=#;                          -- making an instance of the OPERATORS class
104:           loop
105:     	 #OUT + show_stack;
106:     	 line:=#IN.get_str + " ";
107:     	 loop 
108:     	    word := line.split!(' ');
109:     	    word := word.head(word.size-1);
110:     	    if op.is_unop(word) then stack.push(op.apply(word, stack.pop)); -- if unary, pop once, calculate, then push
111:     	    elsif op.is_biop(word) then bicalc(word); -- if binary, call bicalc
112:     	    elsif is_number(word) then  push_to_stack(word); -- push if number
113:     	    elsif word="clear" then clear_stack;   -- clear the stack if "clear" 
114:     	    elsif word="end" then UNIX::exit(0);   -- terminate if "end"
115:     	    end;
116:     	 end;
117:           end;
118:        end;
119:     end;
$ sacomp rpcalc.sa -o rpcalc
$ ./rpcalc                          -- bold strings represent user input
> 2.2 0.8 + 4.7 1.7 - *      -- (2.2 + 0.8) * (4.7 - 1.7)
> 9 0.5 ^                      -- 9 ^ 0.5
> 3 sin                        -- sin(3)
> 0.14112 asin                 -- asin(0.14112)
> 0.141593 10 2 ^              -- 0.141593 (10 ^ 2)
> 0.141593 100 *               -- 0.141593 * 100
> 14.1593 end
$

5. Summery

Sather imports features of functional programming languages and can treat procedures as data. This allow you to write highly abstracted codes and to reuse them.

Download and play with the code presented in this chapter.


HOME Try Sather download Post Messages