2011年10月5日水曜日

MATLAB の無名関数内で分岐を行う方法

MATLAB には一応無名関数がありますが,関数型のプログラミング言語ほど周辺のユーティリティが充実していないので不便なことが結構あります.最近は MATLAB での無名関数の使い方もだいぶわかってきた気がするので,ちょくちょく小技を紹介していこうかと思います.

今回は僕が使っている分岐を実現する方法を紹介します.MATLABにはC言語のような3項演算子や条件分岐関数が無いために,無名関数内では手軽に分岐を行えません.

分岐を簡単に実現するひとつの方法は,あらかじめユーティリティ関数を作っておくことです.
たとえば僕は以下のような関数 switchfunc をパスに入れています.
function [ out ] = switchfunc( varargin )
%%SWITCHFUNC 条件分岐を簡潔に書くための関数
%  SWITCHFUNC( cond_1, value_1,...
%              cond_2, value_2,...
%                     :
%               true, default_value)
%  cond_1,cond_2, の中で 最初に true の値をとるものを
%  cond_k とすると,value_kを値として返します.
%  全ての条件が満たされない場合エラーを返します.

for k=1:2:nargin-1
    if varargin{k}
        out = varargin{k+1};
        return
    end
end
error('switchfunc: no match');
end

これを使うと,たとえばxの値に応じて切り替わる関数を以下のように書けます
f = @(x) ...
    switchfunc( -2 <= x && x <= -1,  x+2, ...
                -1 <  x && x <   1,    1, ...
                 1 <= x && x <=  2,    x);

ezplot(f,[-2,2]);
結果得られる関数 f  のグラフは↓のようになり

自然な表記で切り替えを持つ関数を作れていることがわかります.

しかし,実はこの switchfunc による分岐と 通常の if 文による分岐には大きな違いがあります.
それはswitchfunc を使った場合,実際の条件分岐を行う前に全てのケースについての値が計算されているという点です.
先ほどの例題のように,値の計算に副作用がない場合には速度が多少遅くなる程度の害しかありませんが,plot のような関数を条件に応じて切り替える用途にはそのままでは使えません.

通常のif 文のように条件と合致した時だけ値の評価を行いたい場合は以下のように一工夫します.
swplot = @(num) ...
            feval( ...
                switchfunc(...
                    num==1, @() ezplot(@sin),...
                    num==2, @() ezplot(@cos)));
figure(1), swplot(1)
figure(2), swplot(2)
ポイントはswitchfuncで切り替える値を無名関数にしていることで,帰ってきた結果のみを feval で実行することによって通常のif文と同様に条件に対応する値のみが評価されます. 

MATLAB の無名関数は副作用がない純粋関数しか含まないことを前提に設計されているので,無名関数のなかで完結するようプログラムを書くと自然にLISPのような関数型言語にちかい構造のプログラムになる気がします. MATLAB は元来このようなスタイルを前提とする言語ではないので表記は複雑になりがちですが,関数型言語で使われるテクニックの中にもMATLABで有用なものがあるかもしれませんね.

3 件のコメント:

  1. 面白いですね!
    これ,条件の方は全て事前に実行されちゃう感じですかね?

    返信削除
  2. そうですね.条件の方も無名関数で与えてswitchfunc内で評価すれば良いのでしょうが,MATLABの文法では書くのが面倒ですね~

    返信削除
  3. これで作った無名関数fを-2から2の範囲で積分
    具体的には
    integral(f,-2,2)
    を実行するとエラーがでます。
    これで作った関数は積分できないのでしょうか。

    返信削除