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 であっさり代替というわけにはいかんのだな。うむ。