HOME Sather を試そう download 書き込む

11. データとしての手続き


1. はじめに

Sather は手続きをデータとして取り扱うことができます。つまり、メソッドの引数として渡したり、 手続きを返すメソッドを書くことができます。データとして扱える手続きのことをここではクロージャと呼ぶことにします。 クロージャーは MAP 型の値にもなれます。

Sather のクロージャには通常の手続き (ROUT 型) とイテレーター(ITER 型) の2種類があります。

2. クロージャの使い方

簡単な例を挙げて説明します。
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 closures
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 closures
25:           csqrt:=bind(_.sqrt);
26:           cpow:=bind(_.pow(_));
27:           ccomp:=bind(aux(_));
28:           istep:=bind(_.step!(_,_));
29:     
30:     -- calling closures
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;
説明
19 — 22 クロージャーは引数と返り値を使って宣言します。
25 — 28 手続きの代入は bind を使って行います。bind の中の '_' は引数に対応します。 あまり複雑な手続きは bind を使って書けないので、別にメソッドを用意して、bind でそれを呼び出すようにします。
31 — 37 ROUT 型クロージャを呼び出すときは call メソッドを、 ITER 型クロージャを呼び出すときは call! を使います。

上のコードでは説明のため宣言と代入を分けて書きましたが、一般には次のように同時に行うことが多いです。

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

3. 手続きを引数にとるメソッド

パラメータ付クラスのほとんどに手続きを引数にとるメソッドが定義されています。 ARRAY 型では手続きを引数にとるフィルターリング、マッピング、フォルディング、ソーティングが定義されています。
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. 実用的(?)なプログラム:逆ポーランド語法電卓の改良

前回取り上げた逆ポーランド語法電卓を ROUT 型を使って書き換えてみました。 MAP{STR, ROUT} と OPERATORS クラスを定義することによって少しスマートに書くことができます。
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          -- 二項演算子をハッシュ表に加える。
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         -- 単項演算子をハッシュ表に加える。必要があればもっと加えることもできる。
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      -- 演算子?
40:           return is_biop(s) or is_unop(s);
41:        end;
42:     
43:        is_biop(s:STR):BOOL is          -- 二項演算子?
44:           return biop.has_ind(s);
45:        end;
46:     
47:        is_unop(s:STR):BOOL is          -- 単項演算子?
48:           return unop.has_ind(s);
49:        end;
50:     
51:        apply(s:STR, a:FLTD):FLTD is    -- 単項演算子を使って計算する。
52:           return unop[s].call(a);      -- apply("log", 3.3) のように使う。
53:        end;
54:     
55:        apply(s:STR, a,b:FLTD):FLTD is  -- 二項演算子を使って計算する。
56:           return biop[s].call(a,b);    -- apply("+", 1.1, 2.3) のように使う。
57:        end;                            -- 引数が違えば同じ名前のメソッドを定義できる。
58:     
59:     end;  --OPERATORS
60:     
61:     
62:     
63:     class MAIN is
64:     
65:        const re:REGEXP:=REGEXP::regexp("^[+\\-]?[0-9]+\\.?[0-9]*$", false);  -- 実数かどうかチェックする正規表現
66:        attr stack:STACK{FLTD};
67:        attr op:OPERATORS;
68:     
69:        is_number(word:STR):BOOL is  -- 実数?
70:           return re.match(word);
71:        end;
72:     
73:        bicalc(word:STR) is          -- 二項演算子を使って計算する。
74:           f1,f2 :FLTD;              -- スタックから2回 pop して、計算結果を push する。
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           -- 実数をスタックに積む
81:           stack.push(word.cursor.get_fltd);      
82:        end;
83:     
84:        clear_stack is                      -- スタックを空にする。
85:           a:FLTD;
86:           loop
87:     	 while!(~stack.is_empty);
88:     	 a:=stack.pop;
89:           end;
90:        end;
91:     
92:        show_stack:STR is                    -- スタックを文字列に変換
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:=#;                          -- OPERATORS クラスのインスタンスを作る。
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)); -- 単項演算子ならスタックから pop して計算結果を push する。
111:     	    elsif op.is_biop(word) then bicalc(word); -- 二項演算子なら bicalc を呼ぶ。
112:     	    elsif is_number(word) then  push_to_stack(word); -- 実数ならスタックに積む。
113:     	    elsif word="clear" then clear_stack;   -- "clear" ならスタックを空にする。
114:     	    elsif word="end" then UNIX::exit(0);   -- "end" なら終了。
115:     	    end;
116:     	 end;
117:           end;
118:        end;
119:     end;
$ sacomp rpcalc.sa -o rpcalc
$ ./rpcalc                          -- bold は入力
> 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. 終わりに

Sather は関数型言語の特徴を取り入れて、手続きをデータとして取り扱うことができます。 これによって、抽象化の高いコードを書くことが可能になり、コードの使い回しが促進されます。

ここに示したコードは付録につけておいたので遊んでみてください。


HOME Sather を試そう download 書き込む