Ada を触っての個人的なメモ

あまりこういうチラ裏エントリは書かないつもりでいたけど。

どうしても RHEL4 上で gcc 4.3 のパッケージを作らないといけないという状況にいて、何度も試行錯誤を繰り返す中、途中で出てくる gnat という文字がどうにも気になってしまったので Ada を触ってみた。

第一印象としては、これが 1980 年前後に生まれたとは思えないほど洗練されているなあ、といったところ。もちろん今ある言語と比較したら幾分か古くさいところはあるけども、これが流行らなかったのは登場のタイミングのせいとしか思えない。

いろいろ機能を眺めていて特に興味を惹かれたのが task と rendezvous という、並行プログラミングとメッセージパッシングの機構だ。そういうパラダイムに立つ言語の系統では、最近だと Erlang が真っ先に思いつくわけだけど、可読性や保守性がやや悪い (のではないか)。もしかしたら Ada はその点で優れているのかもしれないと思い、次のようなコードを Ada と Erlang で書いてみた。MyTask1 と MyTask2 という 2 つのタスクがあって、MyTask2 が MyTask1 に hello というメッセージを定期的に送信する、というコードだ。

Ada 版:

with Ada.Text_IO;
with Ada.Strings.Unbounded;
with Ada.Strings.Fixed;

procedure MyTest is
    task MyTask1 is
        entry Hello(Message: String);
    end MyTask1;

    task MyTask2 is
        entry DoIt;
    end MyTask2;

    task body MyTask1 is
        use Ada.Text_IO;
        use Ada.Strings.Unbounded;
        Sent_Message: Unbounded_String;
    begin
        Put_Line("MyTask1 is ready");
        loop
            select
                accept Hello(Message: in String) do
                    Sent_Message := To_Unbounded_String(Message);
                end Hello;
            end select;
            Put_Line("Got message " & To_String(Sent_Message));
            Put_Line("MyTask1 is going to sleep for 5 seconds...");
            delay 5.0;
            Put_Line("MyTask1 woke up");
        end loop;
    end MyTask1;

    task body MyTask2 is
        use Ada.Text_IO;
        use Ada.Strings;
        use Ada.Strings.Fixed;
        type Positive_Integer is range 0 .. Integer'Last;
        package IIO is new Integer_IO(Positive_Integer);
        Count : Positive_Integer := 0;
    begin
        Put_Line("MyTask2 is ready");
        accept DoIt do
            loop
                Put_Line("Sending message to MyTask1");
                declare
                    Tmp: String(1 .. Positive_Integer'Width);
                begin
                    IIO.Put(Tmp, Count);
                    select
                        MyTask1.Hello("world(" & Trim(Tmp, Left) & ")");
                        Put_Line("Finished sending message to MyTask1");
                    or
                        delay 0.0;
                        Put_Line("MyTask1 is busy");
                    end select;
                end;
                delay 1.0;
                Count := Count + 1;
            end loop;
        end DoIt;
    end MyTask2;
begin
    Ada.Text_IO.Put_Line("Wait 5 seconds for MyTask2 to start...");
    delay 5.0;
    MyTask2.DoIt;
end MyTest;

そして Erlang 版:

-module(mytest).
-export([start/0, my_task1/0, my_task2/1]).
-record(hello, {message}).
-record(doit, {}).

my_task1() ->
    io:put_chars("my_task1 is ready\n"),
    my_task1_main()
    .

my_task1_main() ->
    receive
        #hello{message = Message} ->
            io:put_chars("Got message " ++ Message ++ "\n")
    end,
    io:put_chars("my_task1 is going to sleep for 5 seconds...\n"),
    timer:sleep(5000),
    io:put_chars("my_task1 woke up\n"),
    my_task1_main()
    .

my_task2(MyTask1) ->
    io:put_chars("my_task2 is ready\n"),
    receive
        #doit{} ->
            my_task2_doit(MyTask1, 0)
    end
    .

my_task2_doit(MyTask1, Count) ->
    io:put_chars("Sending message to my_task1\n"),
    MyTask1 ! #hello{message = "world (" ++ integer_to_list(Count) ++ ")"},
    io:put_chars("Finished sending message to my_task1\n"),
    timer:sleep(1000),
    my_task2_doit(MyTask1, Count + 1)
    . 
    


start() ->
    MyTask1 = spawn(?MODULE, my_task1, []),
    MyTask2 = spawn(?MODULE, my_task2, [MyTask1]),
    io:put_chars("Wait 5 seconds for my_task2 to start...\n"),
    timer:sleep(5000),
    MyTask2 ! #doit{},
    ok
    .

実行してみると分かる通り、この 2 つの動作は等しくない。

Ada 版:

MyTask2 is ready
MyTask1 is ready
Wait 5 seconds for MyTask2 to start...
Sending message to MyTask1
Finished sending message to MyTask1
Got message world(0)
MyTask1 is going to sleep for 5 seconds...
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
MyTask1 woke up
Sending message to MyTask1
Got message world(5)
MyTask1 is going to sleep for 5 seconds...
Finished sending message to MyTask1
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
MyTask1 woke up
Sending message to MyTask1
Got message world(10)
Finished sending message to MyTask1
MyTask1 is going to sleep for 5 seconds...
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
Sending message to MyTask1
MyTask1 is busy
MyTask1 woke up
Sending message to MyTask1

...

Erlang 版:

Wait 5 seconds for my_task2 to start...
my_task1 is ready
my_task2 is ready
Sending message to my_task1
Finished sending message to my_task1
Got message world (0)
my_task1 is going to sleep for 5 seconds...
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
my_task1 woke up
Got message world (1)
my_task1 is going to sleep for 5 seconds...
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
Sending message to my_task1
Finished sending message to my_task1
my_task1 woke up
Got message world (2)
my_task1 is going to sleep for 5 seconds...
Sending message to my_task1

...

つまり、Ada のメッセージ受信は accept 節に達したところでしか行われない。accept 節に達していないときに entry に対してディスパッチするとブロックする。Erlang は、いつでもメッセージの送信が可能であり (たとえ receive 節がなくても)、送信されたメッセージは FIFO キューに蓄積される。

まあそんなわけで Ada であっさり代替というわけにはいかんのだな。うむ。