14.7の『SHOUTcastサーバ』はこの本の中で2番目の山場だと思う。send_file/5 とか、処理の見通しが悪過ぎて投げ出したくなった……。が、頑張ってなんとか読み進めている。
まず「ファイルの中の、ある範囲内にランダムアクセスする」処理を分離したい。というわけで次のようなモジュールを書いた。一見して明らかなように、露骨にOOを意識している。
-module(filerange). -export([open/2, close/1, pread/3]). -record(file_with_range, { io, min, max }). open(File, Min, Max) -> {ok, Io} = file:open(File, [read, binary, raw]), #file_with_range{io=Io, min=Min, max=Max}. close(#file_with_range{io=Io}) -> file:close(Io). pread(#file_with_range{io=Io, min=Min, max=Max}, Offset, Size) -> Start = Min + Offset, End = Start + Size, if End > Max -> case file:pread(Io, Start, Max - Start) of {ok, Data} -> {less, Data}; eof -> {less, <<>>} end; true -> {ok, Data} = file:pread(Io, Start, Size), {exact, Data} end.
しかしこれではイマイチだ。グローバルなモジュールの名前空間を占有する割には、さほど汎用的なコードではない。というか別に汎用的にするつもりもない。単に処理のスコープを分割したいだけだ。しかしそういう気軽な分割に用いるには、モジュールという単位は大げさ過ぎる……。
そこでふと、以前に「様々なプログラミングパラダイムを全てプロセスとメッセージ送信に還元していくのがErlang流だ」と考えたことを思い出した。そうだ、これも別プロセスで動かしてしまおう!
filerange_server(File, Min, Max) -> {ok, Io} = file:open(File, [read, binary, raw]), filerange_loop(Io, Min, Max). filerange_loop(Io, Min, Max) -> receive {From, {pread, Offset, Size}} -> Start = Min + Offset, End = Start + Size, if End > Max -> case file:pread(Io, Start, Max - Start) of {ok, Data} -> From ! {self(), {less, Data}}; eof -> From ! {self(), {less, <<>>}} end; true -> {ok, Data} = file:pread(Io, Start, Size), From ! {self(), {exact, Data}} end, filerange_loop(Io, Min, Max); {From, close} -> From ! {self(), file:close(Io)} end.
これで名前空間は適切に分割され、汎用の rpc で呼び出せるようになった。デバッグも簡単だ。
erl> Pid = spawn(fun() -> myshout:filerange_server("AtoZ.txt", 5, 20) end).
<0.37.0>
erl> myshout:rpc(Pid, {pread, 0, 5}).
{exact,<<"FGHIJ">>}
erl> myshout:rpc(Pid, {pread, 10, 5}).
{exact,<<"PQRST">>}
erl> myshout:rpc(Pid, {pread, 0, 100}).
{less,<<"FGHIJKLMNOPQRST">>}
さらに調子に乗って play_songs/3 の中の曲選択部分も別プロセスとして分離した結果、「曲データを無限に読み出し続ける」という処理は次のように簡潔な記述になった。
play_songs(Socket, Pid, I) -> {Bin, Header} = rpc(Pid, next_block), write_data(Socket, Bin, {I, Header}), play_songs(Socket, Pid, I+1).
いい感じだ。何となくErlangのプロセスの面白さが分かってきた気がする。
[...] へびにっき 樹上で暮らすヘビのように生きたい « 14章SHOUTcastサーバのリファクタリング [...]