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のプロセスの面白さが分かってきた気がする。