Powered by SmartDoc

OCaml備忘録

目次

基本的にlinuxベース。バージョンは大体3.07とか3.08ぐらい。よく分からない部分もとりあえず書いているので、多分間違い多数。徐々に追加とか修正とかする予定。今のところオブジェクトとモジュールのところの内容は空。

ツールの使い方

起動と終了の仕方

OCamlのインタプリタを起動させるにはシェルから次のように入力する。

[radio@taka radio]$ ocaml
        Objective Caml version 3.08.0
 
#

終了は

# #quit;;

と打ち込む。バイトコンパイルはシェルから

[radio@taka radio]$ ocamlc hoge.ml -o hoge

ネイティブコンパイルは

[radio@taka radio]$ ocamlopt hoge.ml -o hoge

とする。

また次のようにすると起動時にファイルを読み込むことができる

[radio@taka radio]$  ocaml hoge.ml

またライブラリを読み込ませたいときは

[radio@taka radio]$  ocaml unix.cma

などとする。デフォルトではocamlのライブラリは /usr/local/lib/ocaml/にある。先の例だとここからunix.cma というモジュールを探すわけだが、モジュールの種類によってはここにないものもある。例えばTkモジュールなどはここからさらに/labltkというディレクトリに入っているのでこれを指定するときには以下のようにしなくてはいけない。

[radio@taka radio]$  ocaml -I +labltk labltk.cma

ちなみにI はエルではなく大文字のアイである。この後の+で探索するディレクトリを追加する。

 ファイルの読み方は上記のようにインタプリタを起動するときにファイル名を指定してやれば、それをインタプリタで読み込んだことと同じになるわけだが、インタプリタを起動してから読み込むことも可能だ。以下のようにする。

# #use "hoge.ml";;

この例だとhoge.mlというファイルをカレントディレクトリから読み込む。モジュールを読み込むには 

# #load "unix.cma";; 

とする。違うディレクトリのファイルを読み込みたいときは次のようにしてディレクトリを移動する。

# #cd "../";;

この#cdはどうやらホームディレクトリなどは認識しないようだ。つまり次の指定の仕方はエラー。

# #cd "~/sub";;

絶対パスで指定するか、カレントディレクトリを表すドット、一個上のディレクトリを表すドットドットは使えるのでそれを利用する必要がある。蛇足になるかもしれないが、カレントディレクトリのファイルを表示させたいのなら次のようにするのが手っ取り早い気がする。

# #load "unix.cma";;
#  Unix.system "ls";;
a.out      inspect.o   ocamltest.cmi  ocamltest.ml  sub1
inspect.c  inspecttop  ocamltest.cmo  sample1.c     sub2
- : Unix.process_status = WEXITED 0

#loadはモジュールの読み込み命令。systemはそのまま文字列をシェルに実行させるシステムコール。

 もう一つの方法として起動時と同様に探索するディレクトリを追加することもできる。次のようにする。例えばインタプリタ内でlabltkを使いたいときは

# #directory  "/usr/local/lib/ocaml/labltk";;
# #load "labltk.cma";;
# Tk.openTk;;
- : ?display:string -> ?clas:string -> unit -> Widget.toplevel \
    Widget.widget = <fun>

のようにする。なお# load命令でモジュールを読み込んだときは、上記の例のようにプログラム内でモジュール名を指定してやる必要がある。例えばUnixモジュールの中のsystemという関数を使いたいときは

# #load "unix.cma";;
# Unix.system "ls";;

というふうにする。

ただしモジュールを読み込みんだ後にopenという命令を使えば、Unixというふうにつける必要はない。つまり次のようにする。

# #load "unix.cma";;
# open Unix;;
# system "ls";;

 ここまでで注意してほしいのはモジュールの呼び出し方では#loadではファイル名を直接文字列で指定するのに対して、openでは文字列ではなくモジュール名を直接指定する。(ちなみにopenはOCamlでは予約語)この結果、最初の文字は大文字になる(モジュール名は大文字で始まる。例えばunix.systemはバツ。Unix.systemが正しい)。なお#load命令、open命令共にに、ふつうにユーザーがバイトコンパイルした命令も読むことができる。試してみよう。

次のようなファイルtest1.mlがあったとする。

(* test1.ml *)
let func1 a b =  a + b;;

ちなみにletは変数に代入する命令。つまりこのファイルには2つの引数aとbをとってその和をとり返す関数が定義されている。このファイルがあるディレクトリで次のようにしてバイトコンパイルする。

[radio@taka sub2]$ ocamlc -c test1.ml
[radio@taka sub2]$ ls
test1.cmi  test1.cmo  test1.ml

そうするとtest.cmoというファイルができているはずだ。ここでcオプションをつけるのはgccと同じように、他のファイルのソースとリンクさせるためだ。もしこれをつけないと単独で実行可能なファイルができる。このディレクトリで再びocamlを起動しこれを読み込んでみる。

[radio@taka sub2]$ ocaml
        Objective Caml version 3.08.0
 
# #load "test1.cmo";;
# Test1.func1 1 1;;
- : int = 2

2ときちんと表示されたはずだ。今度はopen命令を試してみる。そのまま、open Test1と打ってみる。そうするとTest1と打たなくてもfunc1が使用できるようになっていることが確認できる。

# open Test1;;
# func1 1 1;;
- : int = 2

しつこいようだがopen命令では文字列ではなくモジュール名を直接指定(今回だとTest)し、関数を呼び出すときはモジュール名をつける必要がないことに注意してほしい。ここら辺のことは拡張子のところでもう少しだけ詳しくふれる。

インタプリタ内で#をつけて使う命令をtoplevel directiveというが、これにはほかにもいくつかある。詳しいことはマニュアルの第9章The toplevel system (ocaml)を読んでほしい。ここでは今までに出てきたのを含めてまとめてみる。

ラベルというのは簡単に言うと、関数で引数を一部だけ指定するのですむようにするもの。unixモジュール、labltk,lablgtk,lablglなどで普通に使われている。その時になったら説明する。

拡張子

ocamlでは拡張子がやたらと多い。ml,mll,mly,cmo,cma,cmi,cmx,cmxaなどがある。これらをある程度整理しておかないと混乱する(自分は混乱した)。ので、まとめて整理してみたい。細かいところは間違っていると思うが、そこら辺は自分で調べて欲しい。

mlファイル

 まず説明不要な気もするが、mlという拡張子がソースファイルだ。いうまでもなく一番基本となるファイルだ。

mll、mlyファイル

 mll、mlyはそれぞれocamllex、ocamlyaccのソースコードだ。これは名前から想像できるように字句解析、構文解析で有名なCのlex,yaccのocaml版だ。興味のある人はソースコードのトップディレクトリ/parsingにocamlを字句解析、構文解析している(多分)ocamllexとocamlyaccのファイルがある。見てみてほしい。が、興味のない人は特に関係ない。

cma、cmxa ファイル

ライブラリを表すファイル。コンパイルするときにリンクしたりする。自分で作ったりする方法は後で紹介するとして、一番大事なのはこれらがバイトコードとネイティブコードであるという点だ。例えばunixモジュールを使ったプログラムunix_test1.mlをコンパイルしようとする。

(* unix_test1.ml*)
let _ = Unix.sleep 1;;

ちなみにアンダーバーを関数名にするのはこの関数を実行させたいときの決まり文句のようなものだ。これをバイトコンパイルして実行してみる。

[radio@taka sub2]$ ocamlc unix.cma unix_test1.ml -o unix_test1
[radio@taka sub2]$ ./unix_test1

一秒間止まり、その後にシェルに戻ったはずだ。これと同様なことをネイティブコードでしようとしてもうまくいかない。次のように打つとエラーになる。

[radio@taka sub2]$ ocamlopt unix.cma unix_test1.ml -o unix_test1
/usr/local/bin/ocamlopt: don't know what to do with unix.cma.

ネイティブコードを出すには次のようにしなくてはいけない。

[radio@taka sub2]$ ocamlopt unix.cmxa unix_test1.ml -o unix_test1
[radio@taka sub2]$ ./unix_test1

ocamlcのときと同様に実行できたはずだ。

cmx,cmoファイル

どちらもc言語の拡張子がoのファイルのOCaml版のようなもの。これらのファイルについてはコンパイルについての説明の場所で使い方にもう少し詳しくふれる。

cmiファイル

cmi:ocamlcでもocamloptでも生成されるようだ。コンパイル化されたインターフェース情報を記録しているが、これもcmxと同様に特に気にする必要はない気がする。

mliファイル

インタフェースが定義されているファイル。これは結構重要。何が重要かというとモジュールの型などが書かれている点が重要だ。モジュール、ライブラリを作る立場の人間からすれば少し違うのだろうが使う側の人間にとってみれば、インストールしたライブラリのmliファイルを読めばどんな関数、型がどのように定義されているかがすぐに分かるというのは非常に便利だ。特にOCamのように資料が少ない言語ではこの情報は貴重だ。このようにモジュールのインターフェースを調べるにはmliファイルを見ればよいのだが(標準ライブラリならinfoファイルが一番早いだろうが)、結構面倒だ。このようなときはocamlbrowserを使うと便利だ。例えばlabltkのインターフェースを調べたいのなら次のようにディレクトリを指定してやれば、標準のものに加えてlabltkのインターフェースがGUIで読める。

[radio@taka sub2]$ ocamlbrowser -I +labltk

…しかし、実際にはやはりmliファイルを見ることが多い気がするが。

コンパイルの仕方

拡張子の話のついでというわけでもないが、ここでコンパイルの仕方をまとめてみたい。基本的にはgccと同様だ。つまり基本は次の形になる。

ocamlc hoge.ml -o hoge
ocamlopt hoge.ml -o hoge

なおこれらの最適化のバージョンもある。インストール時に設定をしているとインストールされているはずだ。

ocamlc.opt hoge.ml -o hoge
ocamlopt.opt hoge.ml -o hoge

などとする。コマンド名以外はocamlc,ocamloptと同様だ。これからの説明では基本的に最適化でないほうでするがオプションなども同じなので多分最適化するコンパイラの方でも同様に動くはずだ。なお現在のocamlのコンパイラの最適化はあまり期待しない方がよい。ocamlcとocamloptの実行速度はかなり異なるが、ocamlcとocamlc.opt、もしくはocamloptとocamlopt.optの差はほとんどないような気がする。

ライブラリを指定するにはソースコードの前にライブラリのファイル名を書けばよい。

ocamlc unix.cma hoge.ml -o hoge
ocamlopt unix.cmxa hoge.ml -o hoge

注意事項としてライブラリのファイル名は必ずソースファイルの前に置かなくてはいけない。つまり次のやり方はエラーを起こす。

ocmalc hoge.ml unix.cma -o hoge
ocamlopt hoge.ml unix.cmxa -o hoge

標準のディレクトリに含まれないファイルをリンクするには

ocamlc -I +labltk labltk.cma hoge.ml -o hoge
ocamlopt -I +labltk labltk.cmxa hoge.ml -o hoge

などとすればよい。つまりこれは/usr/local/lib/ocaml/labltkからlabltk.cma , labltk.cmxaを探してリンクしている。 

次に分割コンパイルの例を挙げる。次のようなファイルがあったとする。

(* test2.ml *)
let test2 a = a + 1;;
(*test3.ml *)
let test3 a = a + 2;;

これらをそれぞれコンパイルする。すでに述べたようにcオプションをすると実行ファイルを作らずコンパイルのみになる。

[radio@taka sub2]$ ocamlc -c test2.ml
[radio@taka sub2]$ ocamlc -c test3.ml

これを呼び出すには次のようなファイルtest4.mlを使う。

(* test4.ml *)
let _ = 
 print_int (Test2.test2 2); 
 print_newline();
 print_int (Test3.test3 2);
 print_newline();;      

これをコンパイルして実行するには

[radio@taka sub2]$ ocamlc test2.cmo test3.cmo test4.ml -o test4
[radio@taka sub2]$ ./test4
3
4

とする。このままでもよいのだが、モジュール名が長かったり、そのモジュールの関数を非情にたくさん呼び出したりするときは、いちいちそのモジュール名をつけるのは面倒くさい。前述したようにそのようなときにはopenを使うとモジュール名を省略できる。test4.mlに似た次のようなファイルtest5.mlを用意する。

(* test5.ml *)
open Test2;;
open Test3;;
let _ = 
 print_int (test2 2);
 print_newline();
 print_int (test3 2);
 print_newline();;

これでも同様にコンパイルできる。

[radio@taka sub2]$ ocamlc test2.cmo test3.cmo test5.ml -o test5
[radio@taka sub2]$ ./test5
3
4

今の例ではバイトコードコンパイルだったが、ネイティブコンパイルでも同様なことができる。

[radio@taka sub2]$ ocamlopt -c test2.ml
[radio@taka sub2]$ ocamlopt -c test3.ml
[radio@taka sub2]$ ocamlopt test2.cmx test3.cmx test4.ml -o test4
[radio@taka sub2]$ ./test4
3
4
[radio@taka sub2]$ ocamlopt test2.cmx test3.cmx test5.ml -o test5
[radio@taka sub2]$ ./test5
3
4

cmoファイルがcmxファイルになっていることに注意。

プロファイラの使い方

次にプロファイラの使い方を説明する。OCamlにはプロファイラとデバッガが標準で付いているが、正直言ってあまり使ったことはない。OCamlのプロファイラにはocamlcpでコンパイルしたときのocamlprof、ocamloptでコンパイルしたときのgprofという2つが利用できる。ここでは簡単にその使い方を紹介する。まず、次のようなファイルを用意する。

(* profile_test1_1.ml)
let func1 a = a + 1;;
let _ = for i = 1 to 1000 do func1 i done;;

これを次のようにコンパイルする。

[radio@taka sub2]$ ocamlcp -p a profile_test1_1.ml -o \
    profile_test1_1
File "/tmp/camlpp76fdf6", line 3, characters 27-153:
Warning: this expression should have type unit.

警告が出るが多分コンパイルできるはずだ。この警告を出さないようにするには次のようにする。

[radio@taka sub2]$ ocamlcp -p a -w s profile_test1_1.ml -o \
    profile_test1_1

もしくはソースファイルを変更する。

(* profile_test1_2.ml *)
let func1 a =  a + 1;;
let _ = for i=1 to 1000 do ignore(func1 i) done;;

これで普通にコンパイルできる。ignoreは返値を明示的に無視するOCamlの組み込み関数。

[radio@taka sub2]$ ocamlcp -p a profile_test1_2.ml -o \
    profile_test1_2

次に実行しプロファイルを行う。

[radio@taka sub2]$ ./profile_test1_1
[radio@taka sub2]$ ocamlprof profile_test1_1.ml
(* profile_test1_1.ml *)
let func1 a = (* 1000 *) a + 1;;
let _ = for i=1 to 1000 do (* 1000 *) func1 i done;;
 

 ocamlprofはソースファイルを指定することに注意。func1の前に(*1000 *)と表示されれば成功。つまり何回関数が呼ばれたかが分かる。ちなみに繰り返すとこの数は増える。つまり上に続けてもう一度行うと、

[radio@taka sub2]$ ./profile_test1_1
[radio@taka sub2]$ ocamlprof profile_test1_1.ml
(* profile_test1_1.ml *)
let func1 a = (* 2000 *) a + 1;;
let _ = for i=1 to 1000 do (* 2000 *) func1 i done;;
 

となる。

次はgprofを試してみる。次のようなファイルを用意する。

(* profile_test2.ml *)
let func1 () = Unix.sleep 1;;
let _ =   func1();;

これを次のようにコンパイルしてプロファイルする。

[radio@taka sub2]$ ocamlopt -p unix.cmxa profile_test2.ml -o \
    profile_test2
[radio@taka sub2]$ ./profile_test2
[radio@taka sub2]$ gprof profile_test2

計測結果はいまいち分からないが、とりあえず出来たと思う。あと、結果は長いので省略している。

デバッガの使い方

OCamlのデバッガではgdbなどと違ってソースコードの好きな場所にブレイクポイントを設定することはできない。ブレイクポイントを設定できる場所をイベントなどと呼ぶ。次のbowtieというのがイベントになる。

関数呼び出しの後
(f arg)bowtie
関数実行の始め
fun x y z -> bowtie
パターンマッチ
function part 1 -> bowtie expr1 | ... | -> partN ->bowtie exprN
条件節
if cond then bowtie else bowtie expr2
ループ
for i=a to b do bowtie done

これらについてそれぞれ確認してみる。まず次のようなファイルを用意して、関数呼び出しの後、関数実行のはじめ、そしてforループにブレイクポイントが設定できることを確かめてみる。

(*1*) (* debug_test1.ml*)
(*2*)let func1 () =
(*3*) print_string "a";
(*4*) for i=1 to 3 do 
(*5*)  print_string "b" 
(*6*) done;
(*7*)
(*8*)let _ = func1 ();;

次のようにコンパイルする。

[radio@taka sub2]$ ocamlc -g debug_test1.ml -o debug_test1

次のようにデバッガを起動させ、ブレイクポイントを設定してみる。

[radio@taka sub2]$ ocamldebug debug_test1
        Objective Caml Debugger version 3.08.0
 
(ocd) break @Debug_test1 2
Loading program... done.
Breakpoint 1 at 6160 : file debug_test1.ml, line 3, character 2
(ocd) break @Debug_test1 4
Breakpoint 2 at 6220 : file debug_test1.ml, line 5, character 3
(ocd) break @Debug_test1 8
Breakpoint 3 at 6320 : file debug_test1.ml, line 8, character 17

起動させる。

(ocd) run
Time : 12 - pc : 6160 - module Debug_test1
Breakpoint : 1
3  <|b|>print_string "a";
(ocd) run
Time : 16 - pc : 6216 - module Debug_test1
Breakpoint : 2
5   <|b|>print_string "b"
(ocd) run
Time : 20 - pc : 6216 - module Debug_test1
Breakpoint : 2
5   <|b|>print_string "b"
(ocd) run
Time : 24 - pc : 6216 - module Debug_test1
Breakpoint : 2
5   <|b|>print_string "b"
(ocd) run
Time : 28 - pc : 6316 - module Debug_test1
Breakpoint : 3
8 let _ = func1 ()<|a|>;;
(ocd) run
abbbTime : 44
Program exit.

まず最初にブレイクポイント1でとまり、そこでさらにrunと打ち込んで続けるとブレイクポイント2が3回続き、最後にブレイクポイント3に帰ってくるのが確認できると思う。つまり最初のブレイクポイントは関数実行の始まりで、次のブレイクポイントがforループ、3つめのブレイクポイントが関数呼び出しの終わりになる。

次にブレイクポイントの時点での変数の値を表示させてみる。3つ目のブレイクポイントが終わった後に再び走らせるには次のように打ってプログラムを最初に戻すのが簡単だ。

(ocd) goto 0
Time : 0
Beginning of program.

再びrunで実行して2つ目のブレイクポイントになったら変数の値を表示させてみよう。これにはprint命令かdisplay命令を用いる。またこの命令の省略記法としてp命令とd命令も用意されている。print命令とdisplay命令の違いには後で触れる。

(ocd) run
Time : 12 - pc : 6160 - module Debug_test1
Breakpoint : 1
3  <|b|>print_string "a";
(ocd) run
Time : 16 - pc : 6216 - module Debug_test1
Breakpoint : 2
5   <|b|>print_string "b"
(ocd) print i
i : int = 1
(ocd) display i
i : int = 1
(ocd) p i
i : int = 1
(ocd) d i
i : int = 1
(ocd) run
Time : 20 - pc : 6216 - module Debug_test1
Breakpoint : 2
5   <|b|>print_string "b"
(ocd) print i
i : int = 2
(ocd) display i
i : int = 2

ブレイクポイントの情報を表示するにはinfo命令、ブレイクポイントを削除したいのならdelete命令を使う。

(ocd) info break
Num    Address  Where
  1       6160  file debug_test1.ml, line 3, character 2
  2       6216  file debug_test1.ml, line 5, character 3
  3       6316  file debug_test1.ml, line 8, character 17
(ocd) delete 2
(ocd) info break
Num    Address  Where
  1       6160  file debug_test1.ml, line 3, character 2
  3       6316  file debug_test1.ml, line 8, character 17
(ocd) delete
Delete all breakpoints ? (y or n) y
(ocd) info break
No breakpoints.

デバッガを終了したいときはquitと入力する。

(ocd) quit
The program is running. Quit anyway ? (y or n) y
[radio@taka sub2]$

次にパターンマッチと条件節にブレイクポイントが設定できることを確認する。次のようなファイルを用意する。

(*1*)(*debug_test2.ml*)
(*2*)let func1 a =
(*3*) match a with
(*4*)  1 -> print_int 1;
(*5*) |2 -> print_int 2;
(*6*) |3 -> print_int 3;
(*7*) |_ -> print_int 10;;
(*8*)
(*9*) let func2 a =
(*10*) if a=1
(*11*)  then print_int 1
(*12*)  else print_int 10;;
(*13*)
(*14*) let _ = func1 1;
(*15*)         func1 2;
(*16*)         func1 4;
(*17*)         func2 1;
(*18*)         func2 3;;

これを同様にコンパイルしてデバッガを起動させ、ブレイクポイントが設定できることを確かめる。

[radio@taka sub2]$ ocamlc -g debug_test2.ml -o debug_test2
[radio@taka sub2]$ ocamldebug debug_test2
        Objective Caml Debugger version 3.08.0
 
(ocd) break @Debug_test2 4
Loading program... done.
Breakpoint 1 at 6308 : file debug_test2.ml, line 4, character 13
(ocd) break @Debug_test2 5
Breakpoint 2 at 6332 : file debug_test2.ml, line 5, character 13
(ocd) break @Debug_test2 6
Breakpoint 3 at 6356 : file debug_test2.ml, line 6, character 13
(ocd) break @Debug_test2 7
Breakpoint 4 at 6256 : file debug_test2.ml, line 7, character 13
(ocd) break @Debug_test2 11
Breakpoint 5 at 6176 : file debug_test2.ml, line 11, character 14
(ocd) break @Debug_test2 12
Breakpoint 6 at 6200 : file debug_test2.ml, line 12, character 14
(ocd) run
Time : 13 - pc : 6308 - module Debug_test2
Breakpoint : 1
4 (*4*)  1 -> <|b|>print_int 1;
(ocd) run
Time : 20 - pc : 6332 - module Debug_test2
Breakpoint : 2
5 (*5*) |2 -> <|b|>print_int 2;
(ocd) run
Time : 27 - pc : 6256 - module Debug_test2
Breakpoint : 4
7 (*7*) |_ -> <|b|>print_int 10;;
(ocd) run
Time : 34 - pc : 6176 - module Debug_test2
Breakpoint : 5
11 (*11*)  then <|b|>print_int 1
(ocd) run
Time : 41 - pc : 6200 - module Debug_test2
Breakpoint : 6
12 (*12*)  else <|b|>print_int 10;;
(ocd) run
1210110Time : 62
Program exit.

次にもう少し詳しくprintとdisplay命令の使い方を見ていく。次のようなファイルを用意する。

(* debug_test3.ml)
let _ =
 let a = Array.make 10 0 in
 let b = Array.make 3 [|2;2;2|] in
 let c = [| [| [|2;3|] |] |]  in
  if a.(0)=2 then () else ();;

これを次のようにして実行する。

[radio@taka sub2]$ ocamlc -g debug_test3.ml -o debug_test3
[radio@taka sub2]$ ocamldebug debug_test3
        Objective Caml Debugger version 3.08.0
 
(ocd) break @Debug_test3 6
Loading program... done.
Breakpoint 1 at 6240 : file debug_test3.ml, line 6, character 3
(ocd) run
Time : 14 - pc : 6240 - module Debug_test3
Breakpoint : 1
6   <|b|>if a.(0)=2 then () else ();;

こうして、p命令とd命令をそれぞれの変数について打ってみる。

(ocd) p a
a : int array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|]
(ocd) p b
b : int array array = [|[|2; 2; 2|]; [|2; 2; 2|]; [|2; 2; 2|]|]
(ocd) p c
c : int array array array = [|[|[|2; 3|]|]|]
(ocd) d a
a : int array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|]
(ocd) d b
b : int array array = [|$1; $2; $3|]
(ocd) d c
c : int array array array = [|$4|]

print文のほうはすべて表示されたがdisplayの方はbとcはきちんと表示されなかったはずだ。これはdisplay文が深さ1までのものしか表示しないせいだ。print命令の深さは例えば次のようにして設定することが可能だ。

(ocd) set print_depth 2

このようにしてprint命令を使うとcだけがきちんと表示されない。

(ocd) p a
a : int array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|]
(ocd) p b
b : int array array = [|[|2; 2; 2|]; [|2; 2; 2|]; [|2; 2; 2|]|]
(ocd) p c
c : int array array array = [|[|$9|]|]

このように表示する深さを設定することができる。また一度に表示する長さも指定することが出来る。例えば次のように入力する。

(ocd) set print_length 5

このようにしてprint命令とdisplay命令を行ってみる。表示される長さが変わっているのを確認できる。

(ocd) p a
a : int array = [|0; 0; 0; 0; ...|]
(ocd) p b
b : int array array = [|[|2; 2; 2|]; ...|]
(ocd) p c
c : int array array array = [|[|$10|]|]
(ocd) d a
a : int array = [|0; 0; 0; 0; ...|]
(ocd) d b
b : int array array = [|$11; $12; $13|]
(ocd) d c
c : int array array array = [|$14|]

あとデバッガで知っておいた方がよいコマンドは

set program ファイル名
set arguments 引数

ぐらいだろう。これでプログラムとその引数を指定する。 

Cとのインターフェース

OCamlからCの関数を呼ぶには、いくつか方法がある。swig,camlIDLを使うなどの方法があるが、ここでは最も基本的な使い方を説明する。まずはOCamlからCの関数を呼べることを確かめる。

(* withc1.ml *)
external func:int->int->int->int = "func_c";;
let _ =
 print_int (func 2 3 4);
 print_newline ();;
/* withc1.c */
#include <caml/mlvalues.h>
value func_c(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
[radio@taka sub2]$ gcc -I /usr/local/lib/ocaml -c withc1.c -o withc1.o
[radio@taka sub2]$ ocamlc -custom withc1.ml withc1.o -o withc1
[radio@taka sub2]$ ./withc1
9

このようにCの関数をOCamlから呼べることが確認できる。このときmlファイルのexternalというので呼ぶ外部関数を定義している。そしてCのLong_val(x)などで、OCamlのデータ構造をCのデータ構造に戻し、Val_longでさらにCのデータ構造をOCamlのデータ構造に戻している。つまりOCamlのデータ型はすべてvalue型となっているので、Cで使うときはこれをCに理解できる形に変換して使用する。

これはOCamlの外部宣言を書いているファイルに実行する内容を書いたが、このファイルを分けることも出来る。

(* withc2_1.ml *)
external func:int->int->int->int = "func_c";;
(* withc2_2.ml *)
let _ =
 print_int (Withc2_1.func 2 3 4);
 print_newline();;
/* withc2.c */
#include <caml/mlvalues.h>
value func_c(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
[radio@taka sub2]$ gcc -I /usr/local/lib/ocaml -c withc2.c -o withc2.o
[radio@taka sub2]$ ocamlc -c withc2_1.ml
[radio@taka sub2]$ ocamlc -custom withc2.o withc2_1.cmo withc2_2.ml -o withc2_2
[radio@taka sub2]$ ./withc2_2
9

またインタプリタからCの関数を呼ぶことも出来るが、これにはインタプリタをocamlmktopで新しく作る必要があるようだ。以下に例を挙げる。ソースファイルは上と同じものを使用する。

[radio@taka sub2]$ gcc -I /usr/local/lib/ocaml -c withc2.c -o withc2.o
[radio@taka sub2]$ ocamlmktop -custom -o mytop1 withc2.o withc2_1.ml
[radio@taka sub2]$ ./mytop1
        Objective Caml version 3.08.0
 
# Withc2_1.func 1 2 3;;
- : int = 6

上の例ではcmoファイルを作っていたが、ソースファイルの数が多くなるといちいち全てのcmoファイルを打つのは大変だ。そこでoファイルからaファイルを作れるようにocamlでもcmoファイルからcmaファイルを作ることが出来る。例えば次のような感じになる。ここではmlファイルはそれぞれCの関数を呼んでいるが、もちろん純粋なmlファイルでも同じように出来る。

/* withc3_1.c */
#include <caml/mlvalues.h>
value funcc3_1(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
/* withc3_2.c */
#include <caml/mlvalues.h>
value funcc3_2(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
/* withc3_3.c */
#include <caml/mlvalues.h>
value funcc3_3(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
/* withc3_4.c */
#include <caml/mlvalues.h>
value funcc3_4(value x,value y,value z){
 return Val_long(Long_val(x)+Long_val(y)+Long_val(z));
}
(* withc3_1.ml *)
external func3_1:int->int->int->int = "funcc3_1";;
external func3_2:int->int->int->int = "funcc3_2";;
(* withc3_2.ml *)
external func3_3:int->int->int->int = "funcc3_3";;
external func3_4:int->int->int->int = "funcc3_4";;

これら6つのファイルがあったとき、OCamlで利用できるひとつのライブラリファイルを作ることを考える。まず次のように打ってcmaファイルを作る。

[radio@taka sub2]$ ocamlc -c withc3_1.ml
[radio@taka sub2]$ ocamlc -c withc3_2.ml
[radio@taka sub2]$ gcc -I/usr/local/lib/ocaml -c withc3_1.c -o withc3_1.o
[radio@taka sub2]$ gcc -I/usr/local/lib/ocaml -c withc3_2.c -o withc3_2.o
[radio@taka sub2]$ gcc -I/usr/local/lib/ocaml -c withc3_3.c -o withc3_3.o
[radio@taka sub2]$ gcc -I/usr/local/lib/ocaml -c withc3_4.c -o withc3_4.o
[radio@taka sub2]$ ocamlc -custom -a -o withc3.cma withc3_1.cmo withc3_2.cmo withc3_1.o withc3_2.o withc3_3.o withc3_4.o

次に、これが呼び出せることを確かめる。

(* withc3.ml *)
open Printf
let _ =
 printf "Withc3_1.func3_1:%d\n" (Withc3_1.func3_1 1 2 3);
 printf "Withc3_1.func3_2:%d\n" (Withc3_1.func3_2 1 2 3);
 printf "Withc3_2.func3_3:%d\n" (Withc3_2.func3_3 1 2 3);
 printf "Withc3_2:func3_4:%d\n" (Withc3_2.func3_4 1 2 3);;
[radio@taka sub2]$ ocamlc withc3.cma withc3.ml -o withc3
[radio@taka sub2]$ ./withc3
Withc3_1.func3_1:6
Withc3_1.func3_2:6
Withc3_2.func3_3:6
Withc3_2:func3_4:6

基本的な文法

基本的な型

OCamlでは強く型付けされた言語であり、型に対する理解は非情に重要になる。そこでまずOCamlの型の中でも基本的ないくつかの型について整理してみたい。

int
-2^30から+2^30までの整数。つまり-1073741824から1073741823の整数。64bitの環境では-2^62から2^62-1までの整数。
float
Cでいうdouble型と同じ。IEEE754に従っている。
char
文字。8bitの整数。つまり0から255までの整数。現在の実装では0から127までの数はASCII標準に従い、128から255はISO 8859-1に従う。
string
文字列。現在の実装では2^24-5の文字数(つまり16777211)の文字列をサポートしている。64bitの環境では2^57-9個の文字列をサポートしている。

次にこれらの基本データ型について許される操作などを見ていく。まずocamlインタプリタを起動する。

[radio@taka sub2]$ ocaml
        Objective Caml version 3.08.0

int型

まずint型。これらは当然四則演算ができる。

# 1+2;;
- : int = 3
# 2-1;;
- : int = 1
# 1*2;;
- : int = 2
# 3/2;;
- : int = 1

自分のマシーンがサポートしている最大のint型、最小のint型はそれぞれmax_int,min_intで調べられる。

# max_int;;
- : int = 1073741823
# min_int;;
- : int = -1073741824

int型を表示するのはprint_int命令を使う。

# print_int 1;;
1- : unit = ()

他にもいくつかの関数が定義されている。

# succ 3;;
- : int = 4
# pred 2;;
- : int = 1
# abs (-2);;
- : int = 2
# 7 mod 3;;
- : int = 1

上から順に、1を足す、1を引く、絶対値を求める、剰余を求める、という関数だ。このうち、後ろ2つの関数には少し注意してほしい。まずmodは四則演算のようにmodを真ん中に書く。あとabsは関数であるのだがかっこを忘れるとこのままではエラーになる。かっこを付けないのならばマイナスの演算子を変える。つまり次のような感じにする。

# abs -3;;
This expression has type int -> int but is here used with type \
    int
# abs (-3);;
- : int = 3
# abs ~-3;;
- : int = 3

このかっこの忘れはOCamlのプログラムでのエラーのけっこうな量を占めている気がする。気を付けて欲しい。

次にint型をとるビット操作を取り上げる。int型を2進数と考えて、and,or,xor,notのビット演算が行える。

# 2 land 4;;
- : int = 0
# 2 lor 4;;
- : int = 6
# 4 lxor 3;;
- : int = 7
# lnot 3;;
- : int = -4

float型

float型。最初必ずはまるのはOCamlではfloat型の四則演算はint型とは異なるということだ。float型の四則演算は.を付ける。

# 1.2 +. 2.2;;
- : float = 3.40000000000000036
# 3.2 -. 3.2;;
- : float = 0.
# 4.0 *. 2.1;;
- : float = 8.4
# 4.5 /. 1.2;;
- : float = 3.75

また四則演算に加えてfloat型には累乗の演算子が定義されている。

# 2.0 ** 4.0;;
- : float = 16.

またint型のように、絶対値を求める、剰余を求める、表示する、のそれぞれの関数が定義されている。

# abs_float (-3.2);;
- : float = 3.2
# mod_float 4.0 3.2;;
- : float = 0.799999999999999822
# print_float 2.3;;
2.3- : unit = ()

またint型のようにかっこを付けないようにもできる。

# abs_float ~-.3.5;;
- : float = 3.5

自分のマシンの最大のfloatと最小のfloatはmax_floatとmin_floatで見ることが出来る。

# max_float;;
- : float = 1.79769313486231571e+308
# min_float;;
- : float = 2.22507385850720138e-308

なぜかint型と違い、最小のmin_floatは負ではない。またおもしろいところではOCamlではfloat型にinfinity(無限大)が定義されている。

# infinity;;
- : float = infinity
# neg_infinity;;
- : float = neg_infinity

他にもepison_floatやnanなどが特別な値として定義されている。詳しくはManual Chapter 19を読んでほしい。

char型

キャラクター型はシングルクオテーションで表す。

# 'a';;
- : char = 'a'
# 'B';;
- : char = 'B'

基本的な関数として、int型をcharに変換したり、char型をint型に変換したりする関数がある。

# int_of_char 'a';;
- : int = 97
# char_of_int 102;;
- : char = 'f'

string型

文字列型はダブルクオテーションで表す。

# "abcde";;
- : string = "abcde"
# "erte";;
- : string = "erte"

基本的な操作として連結がある。

# "abd" ^ "def";;
- : string = "abddef"

ちなみにOCamlでは文字列は変更可能だ。例として次のように打ち込んでみてほしい。

# let a = "abc";;
val a : string = "abc"
# a.[1] <- 'l';;
- : unit = ()
# a;;
- : string = "alc"

aの内容が変更されているのが確認できたと思う。あと、このように文字列から文字を取り出すには

# "abcde".[2];;
- : char = 'c'

などとする。

文字列の中でダブルクオテーションなどを使いたいときは\を組み合わせて使う。

# print_endline "\'";;
'
- : unit = ()
# print_endline "\"";;
"
- : unit = ()
# print_endline "\\";;
\
- : unit = ()

print_endlineは文字列を表示し、最後に改行を付け加える組み込み関数。

bool型

bool値。OCamlはJavaのように真偽を表すために特別の型が定義されていて、いくつかの演算子が利用できる。これをbool型といい、trueとfalseの二つの値が定義されている。

# true;;
- : bool = true
# false;;
- : bool = false

これらの演算は次の5つ。

# not true;;
- : bool = false
# true && false;;
- : bool = false
# true & false;;
- : bool = false
# true or false;;
- : bool = true
# true || false;;
- : bool = true

notは否定、&&と&はand、orと||はorを表すが、マニュアルによると&とorは使うべきではないようだ。つまり、&&と||を使う。

ref型

ここで使われているrefというのは参照型と呼ばれるものだ。OCamlは基本的に変数に対して破壊的代入はできない。つまり、OCamlで普通のint型の値をインクリメントし、再びその変数に代入したりすることは出来ない。Cなどの感じで書くと次のようになるが、見て分かるとおり変数の値は更新されない。

# let a = 1;;
val a : int = 1
# a = a+1;;
- : bool = false

これは二つの値を比べて等しいかを返すだけで代入はできない。このような操作をするためにOCamlは特別なデータ型を用意しており、これをref型という。次のように使う。

# let a = ref 0;;
val a : int ref = {contents = 0}
# !a;;
- : int = 0
# a := 2;;
- : unit = ()
# !a;;
- : int = 2
# a := !a+1;;
- : unit = ()
# !a;;
- : int = 3

つまり、ref型の変数aの値を取り出すには!a、代入には:=を使う。ref型はint型に対する参照だけでなくあらゆるものに対して用いることが出来る。

# let a = ref 3.2;;
val a : float ref = {contents = 3.2}
# !a;;
- : float = 3.2
# a := !a +. 1.0;;
- : unit = ()
# !a;;
- : float = 4.2
# let b = ref 'c';;
val b : char ref = {contents = 'c'}
# !b;;
- : char = 'c'
# b := 'g';;
- : unit = ()
# !b;;
- : char = 'g'

また、int型へのref型にはインクリメントとデクリメントの関数incrとdecrが特別に用意されている。

# let a = ref 1;;
val a : int ref = {contents = 1}
# incr a;;
- : unit = ()
# !a;;
- : int = 2
# decr a;;
- : unit = ()
# !a;;
- : int = 1

複合的な型

配列

配列の例は[|1;2;2|]などだ。リストと違い変更可能になっている。また添え字はCのように0からとなっている。

# let a = [|1;2;2|];;
val a : int array = [|1; 2; 2|]
# a.(1);;
- : int = 2
# a.(1) <- 10;;
- : unit = ()
# a;;
- : int array = [|1; 10; 2|]

リストと同じで配列内はすべて同じ型でなくてはいけない。つまり同じ配列内にint型とfloat型がある次のような定義はエラーとなる。

# let a = [|1;2;2.3|];;
This expression has type float but is here used with type int

また上の例でも挙げたが、要素を取り出すには

# a.(2);;
- : int = 2
# a.(1);;
- : int = 10

などとする。また要素を変更するには

# a.(2) <- 3;;
- : unit = ()
# a.(1) <- 9;;
- : unit = ()

などとする。当然、配列を操作する関数は標準関数として用意されているが、これらは標準関数の説明のところでふれる。

リスト

リストの例は[1;2;2]などだ。配列と同様に、リストはすべての型が等しいことを要求する。

# [1;2;2];;
- : int list = [1; 2; 2]
# [1;2;3.2];;
This expression has type float but is here used with type int

リストに要素を付け加えるのは::、リスト同士を結合させるには@演算子を使う。

# 1::[1;2;3];;
- : int list = [1; 1; 2; 3]
# [1;2;3]@[4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]

タプル

タプルはリスト、配列と違い異なる型をとれる。配列やリストに比べてそういった意味では自由度は高いが、適用できる操作や関数は配列、リストのほうが機能的に優れている。次のように定義する。

# (1,1.2,'a',"aa",[|1;2|]);;
- : int * float * char * string * int array = (1, 1.2, 'a', "aa", \
    [|1; 2|])

これは複数の値をまとめて取り扱うのに便利だが、schemeのリストのように使えるわけではない。タプルの値を簡単に取り出すには次のようにする。

# let a,b,c = (1,1.2,'a');;
val a : int = 1
val b : float = 1.2
val c : char = 'a'

またタプルの要素が2つのときには

# fst (1,2.3);;
- : int = 1
# snd (1,2.3);;
- : float = 2.3

という最初と2つめのタプルを取り出す関数が用意されている。これより複雑な操作をしたいときは後述のパターンマッチを利用する。

レコード

OCamlではユーザーが独自のデータ型を定義できる。これにはtypeというキーワードを使う。まずは最も単純な名前なしのものを取り上げる。これは簡単に言ってしまえばtupleに名前を付けたものだ。次のように定義する。

# type ex1 = float * float;;
type ex1 = float * float

これであらたな型が定義できたわけだが、次のようにしても、float*floatのタプルとしてしか表示されない。

# (1.2,2.3);;
- : float * float = (1.2, 2.3)

これはOCamlが最も一般的な型を表示するためで、次のように明示的に型を指定してやれば型が定義できていることを確認できる。

# ((1.2,2.3):ex1);;
- : ex1 = (1.2, 2.3)

実際はレコード型を使うときはフィールド変数に名前を付けることが多い。以下に実際の例を挙げる。

# type complex = {re:int; im:int};;
type complex = { re : int; im : int; }
# {re=2;im=3};;
- : complex = {re = 2; im = 3}
# let a = {re=3;im=4};;
val a : complex = {re = 3; im = 4}

この新しく定義した型をとる関数も簡単に定義できる。

let add_complex a b = { re = a.re + b.re; im= a.im + b.im };;
add_complex {re=1;im=2} {re=3;im=4};;
# add_complex {re=3;im=3} {re=5;im=9};;
- : complex = {re = 8; im = 12}

他にもいくつか例を挙げる。

# type ratio = {num:int;denum:int};;
type ratio = { num : int; denum : int; }
# let add_ratio r1 r2 = { num = r1.num*r2.denum + \
    r2.num*r1.denum; denum=r1.denum*r2.denum};;
val add_ratio : ratio -> ratio -> ratio = <fun>
# add_ratio {num=1;denum=4} {num=5;denum=8};;
- : ratio = {num = 28; denum = 32}
# type vector = {x:float; y:float};;
type vector = { x : float; y : float; }
# let norm a = sqrt(a.x**2.0 +. a.y**2.0);;
val norm : vector -> float = <fun>
# norm {x=2.3;y=4.6};;
- : float = 5.1429563482495162

またレコード型は一度値が決まったら変更できない。つまり上のcomplex型を例として使うと、次のように定義した変数aは参照することは出来ても変更することはできない。

# let a = {re=1; im=4};;
val a : complex = {re = 1; im = 4}

これを変更できるようにするにはcomplex型の定義を次のように変更しなくてはいけない。

# type complex2 = {mutable re2:int; mutable im2:int};;
type complex2 = { mutable re2 : int; mutable im2 : int; }

このmutableというのが、その変数の変更を許すキーワードとなる。次のように使う。

# let a = {re2=3;im2=5};;
val a : complex2 = {re2 = 3; im2 = 5}
# a.re2 <- 10;;
- : unit = ()
# a;;
- : complex2 = {re2 = 10; im2 = 5}

なおこの場合complex2として次のようにすると、以前の定義をつぶしてしまう。

# type complex2 = {mutable re:int; mutable im:int};;
type complex2 = { mutable re : int; mutable im : int; }

この以前の定義をつぶしてしまう操作はインタプリタで#useを使ったり、まとめて関数群を張り付けたりしたとき問題が起こすことが多い。次の例をインタプリタに順に打ち込むとエラーを起こす。

# type complex = {re:int; im:int};;
type complex = { re : int; im : int; }
# let add_complex a b = {re=a.re+b.re; im=a.im+b.im};;
val add_complex : complex -> complex -> complex = <fun>
# let a = {re=2;im=4};;
val a : complex = {re = 2; im = 4}
# type complex = {re:int; im:int};;
type complex = { re : int; im : int; }
# let b = {re=3;im=7};;
val b : complex = {re = 3; im = 7}
# add_complex a b;;
This expression has type complex but is here used with type \
    complex

このエラーは最初のcomplexと2つめのcomplexが違う型だとインタプリタが判断するためだ。この例のようにThis expression has type complex but is here used with type complexのような一見すると良く分からないエラーメッセージが出たら定義を変なところで更新していないかを疑ってみるべきだ。

バリアント

バリアント型はいくつかの値のどれかをとることができる。次のように宣言する。

# type sign = Positive | Negative;;
type sign = Positive | Negative

ここでのPositiveとNegativeはコンストラクタといい、必ず大文字で始めなくてはいけない。次のようにコンストラクタを通じてバリアント型は使う。

# Positive;;
- : sign = Positive
# Negative;;
- : sign = Negative
# let func a = if a>=0 then Positive else Negative;;
val func : int -> sign = <fun>
# func 4;;
- : sign = Positive
# func ~-4;;
- : sign = Negative

コンストラクタに値を持たせることでバリアント型に値を持たせることも出来る。値を持つバリアント型は次のように定義する。

# type number = Int of int | Float of float | Error;;
type number = Int of int | Float of float | Error

これはIntというコンストラクタはintをひとつとり、Floatというコンストラクタはfloatをひとつとるという意味だ。引数を使って呼び出すには次のようにする。

# Int(2);;
- : number = Int 2
# Float(4.32);;
- : number = Float 4.32
# Error;;
- : number = Error

間違った引数を与えるとエラーになる。

# Int(2.43);;
This expression has type float but is here used with type int
# Float(5);;
This expression has type int but is here used with type float
# Error(2);;
The constructor Error expects 0 argument(s),
but is here applied to 1 argument(s)

上のようにコンストラクタの後にofを付けて型を指定するわけだが、ofのあとは上のような単純なものだけでなくいろんなものがこれる。

簡単にこれらを試してみる。

# type sample1 = Sample1 of int * float * char;;
type sample1 = Sample1 of int * float * char
# type sample2 = Sample2 of (int->int);;
type sample2 = Sample2 of (int -> int)
# type sample3 = Sample3 of int * int * sample3 | Nil;;
type sample3 = Sample3 of int * int * sample3 | Nil

複雑なコンストラクタを持つバリアント型の利用はパターンマッチの説明のところで詳しく説明する。

なおバリアント型では基本的に値を変更できない。例えば次の例ではaの値は変更できない。

# type sample = Int of int;;
type sample = Int of int
# let a = Int(4);;
val a : sample = Int 4

例外

OCamlは例外処理をサポートしている。例外処理は他の言語と同じような役割に加え、break文の代わりに使ったりもする(OCamlにはbreak文がないので)。例外はexceptionで定義し、raiseで起こし、try withで捕まえる。例を挙げる。次のようなファイルを用意しインタプリタから読み込む。

(* ex_test1.ml *)
exception Ex1;;
let func1 () =
 try
  print_endline "a";
  raise Ex1;
  print_endline "b";
 with
  Ex1 -> print_endline "c";;

let _ = func1 ();;
[radio@taka sub2]$ ocaml ex_test1.ml
a
c

例外が発生し、捕まえていることが確認できる。また例外はバリアント型の一種のようなものなので値を持つことも出来る。例を挙げる。

(* ex_test2.ml *)
exception Ex2 of int;;
let func2 () =
 try
  print_endline "a";
  raise (Ex2 4);
  print_endline "b";
 with
  Ex2 i -> print_int i; print_newline();;
let _ = func2 ();;
[radio@taka sub2]$ ocaml ex_test2.ml
a
4

発生させた例外の種類だけでなく、それが持っている整数値が例外処理で利用できていることが分かる。

while文から抜け出すためにbreak文を利用するには、例えば次のような感じになる。

(* ex_test3.ml *)
exception Break;;
let func3 () =
 let a = ref 0 in
  try
   while true do
    print_int !a; print_newline(); incr a;
    if !a>5 then raise Break;
   done;
  with
   Break -> print_endline "break";;
let _ = func3 ();;
[radio@taka sub2]$ ocaml ex_test3.ml
0
1
2
3
4
5
break

これまでの例で分かるように、例外処理を関数に組み込もうとするとプログラムの可読性は悪くなる。この可読性を保つためには、例外処理と関数の実態を分けておくことが有効だ。つまり、tryとwithの間に例外が発生する可能性のある関数を含める。次の例を見てほしい。

(* ex_test4.ml *)
exception Break;;
let func4 a =
 while true do
  print_int !a; print_newline(); incr a;
  if !a>5 then raise Break;
 done;;
let _ =
 try
  func4 (ref 0)
 with
  Break -> print_endline "break";;
[radio@taka sub2]$ ocaml ex_test4.ml
0
1
2
3
4
5
break

このように関数の働きを変えずに、関数の処理と例外処理を分けることが出来る。

制御構造と代入

関数と変数の定義の仕方

もう使っているが変数は

# let a = 2;;
val a : int = 2

などと定義する。関数もletで定義する。

# let func1 a = a + 1;;
val func1 : int -> int = <fun>
# let func2 () = print_endline "a";;
val func2 : unit -> unit = <fun>

再帰関数はlet recを使う。

# let rec func3 a = if a=0 then 1 else a + func3 (a-1);;
val func3 : int -> int = <fun>

呼び出すには

# func1 3;;
- : int = 4
# func2 ();;
a
- : unit = ()
# func3 4;;
- : int = 11

などとする。

letには単純な上に上げたようなものの他に、andを使い同時に複数の変数を定義する方法もある。

# let a=3 and b=3.2 and c='a';;
val a : int = 3
val b : float = 3.2
val c : char = 'a'

ローカル変数を定義するには次のようにする。

# let local=3 in local*local;;
- : int = 9
# local;;
Unbound value local

上の例でlocalはローカル変数として定義されているのでinの中でのみ有効になり、inを抜けた後は参照できなくなっていることが確かめられる。

また、letはネストすることができる。

# let a = 3 in let b = 2 in a+b;;
- : int = 5

またlet andの文は同時に行われる。よって次の文はエラー。

# let aa = 2 and bb = aa+2 in bb;;
Unbound value aa

このようにしたいときは次のようにする。

# let aa = 2 in let bb = aa+2 in bb;;
- : int = 4

andとinを組み合わせることもできる。

# let a=4 and b=3 in a*b;;
- : int = 12

またOCamlでは普通の関数だけでなく中置演算子の関数を作れる。例を挙げる。

(* op_test1.ml *)
let (++) a b =
 let r = ref a in
  for i=1 to b do
   r:= !r * a
  done;
  !r;;
let _ =
 print_int (3 ++ 4);
 print_newline ();;
[radio@taka sub2]$ ocaml op_test1.ml
243

OCamlは関数型の言語なので当然匿名関数などが作れる。つまりschemeなどのlambdaのようなキーワードがある。ここら辺の機能について見ていく。

最も基本的なのはfunctionだ。これは一引数の関数を作る。例えば次のような感じになる。

# function x -> x*x;;
- : int -> int = <fun>

この関数を実行したいときは次のようにすればよい。

# (function x-> x*x) 3;;
- : int = 9

当然、変数に束縛させることも出来る。

# let func = function x-> x*x;;
val func : int -> int = <fun>
# func 3;;
- : int = 9

今までの関数定義の仕方は、上の定義の仕方のシンタックスシュガーになる(多分)。

functionで複数の変数を使いたいときは、次のようにする。

# (function x -> function y -> x*y) 2 3;;
- : int = 6

ここらへんはOCamlというか関数型言語の特徴っぽいところなのだが、ここではこれ以上触れない。興味のある人はDeveloping applications with Objective CamlのChapter 2を参照してほしい。

理論的にはともかく実際のプログラムでいちいちfunctionをたくさん書いていては、面倒くさくてかなわない。functionのシンタックスシュガーとして複数の引数をとることが出来るfunという関数が用意されている。次の2つの表現は等価だ。

さっきのfunctionの2変数の例はfunを使ってつぎのように書ける。

# (fun x y -> x*y) 2 3;;
- : int = 6

ここでletによる関数定義の仕方に戻る。つまり関数定義は機能的に同じ複数の仕方で行うことが出来る。例を挙げる。

# let func1 () = print_endline "func";;
val func1 : unit -> unit = <fun>
# let func2 a = a + 1;;
val func2 : int -> int = <fun>
# let func3 a b = a + b;;
val func3 : int -> int -> int = <fun>

などはそれぞれ

# let func1 = function () -> print_endline "func";;
val func1 : unit -> unit = <fun>
# let func2 = function a -> a + 1;;
val func2 : int -> int = <fun>
# let func3 = function a -> function b -> a + b;;
val func3 : int -> int -> int = <fun>

などと等価になる。さらに3番目の例は

# let func3 = fun a b -> a + b;;
val func3 : int -> int -> int = <fun>

などとも等価になる。

関数定義はここで挙げた単純なものだけでなく型制限、ラベルづけなどもできるが、後にしてとりあえず先に進む。

if文

次にif文でのポイントだが、then節とelse節は同じ型を返さなくてはいけない。つまり次の文はエラー

# if 1=2 then 1 else ();;
This expression has type unit but is here used with type int

次のようにすればエラーは消える。

# if 1=2 then ignore(1) else ();;
- : unit = ()

ignoreは返値をunitにする組み込み関数。

またthen節のあとにセミコロンを付けると違う意味になるのでこれも注意が必要。これはthen節の後にセミコロンを書くとelse節はないものとOCamlがみなすせいだ。例を挙げる。

# let func1 a b =
   if a=b then print_endline "a=b";
   print_endline "return";;
val func1 : 'a -> 'a -> unit = <fun>
# let func2 a b =
   if a=b then print_endline "a=b";
   else print_endline "return";;
Syntax error

func3は正しいが、func4はエラーになっている。ではif文の中で複数の命令を実行したいときはどうするかといえばbegin命令を使う。これはいくつかの命令を1つにまとめる命令だ。次のように使う。プログラムが少し長くなるのでファイルに書き込み、それを読み込むことにする。

(* if_test1.ml *)
let func3 a b =
 if a=b then
  begin
   print_endline "1";
   print_endline "2";
   print_endline "3";
  end
 else
  print_endline "false";;
let _
 func3 3 3;
 func3 3 4;;
[radio@taka sub2]$ ocaml if_test1.ml
1
2
3
false

begin節の中にunit型でないものがあると警告が出る。例を挙げる。

(* if_test2.ml *)
let func4 a b =
 if a=b then
  begin
   print_endline "1";
   a + b;
   print_endline "3";
  end
 else
  print_endline "false";;
let _ =
 func4 3 3;
 func4 3 4;;
[radio@taka sub2]$ ocaml if_test2.ml
File "if_test2.ml", line 6, characters 3-8:
Warning: this expression should have type unit.
1
3
false

この警告をなくすにはignoreによってソースを変えるかインタプリタを起動させるときにオプションを付け加える。まずソースを変更する方法を試してみる。変更したソースは次の通り。

(* if_test3.ml *)
let func5 a b =
 if a=b then
  begin
   print_endline "1";
   ignore(a+b);
   print_endline "3";
  end
 else
  print_endline "false";;
let _ =
 func5 3 3;
 func5 3 4;;
[radio@taka sub2]$ ocaml if_test3.ml
1
3
false

警告が出ていなくなっているのが分かる。次にオプションを付け加える方法を試してみる。

[radio@taka sub2]$ ocaml -w s if_test2.ml
1
3
false

こうしても警告が出ていなくなっているのが分かる。

なおif expr1 then expr2;などとするとelse節は省略できるがこれはelse ()を自動的に補っているためだ。だからelse節を省略したときはif節はunit型である必要がある。

# if 1=2 then 1;;
This expression has type int but is here used with type unit
# if 1=2 then ();;
- : unit = ()

両方の節とも同じならunit型である必要はない。

# if 1=2 then 2 else 3;;
- : int = 3

比較演算子

OCamlでは比較演算子もCなどとは違う。知っていれば戸惑うことはないと思うが、知らないと困ることがある。ので、まとめておく。

例えば次のように打ってみてほしい。

# let a = 1;;
val a : int = 1
# let b =1;;
val b : int = 1
# if a==b then print_endline "true" else print_endline "false";;
true
- : unit = ()

trueと表示されたはずだ。では

# let c = "a";;
val c : string = "a"
# let d = "a";;
val d : string = "a"
# if c==d then print_endline "true" else print_endline "false";;
false
- : unit = ()

とやってみるとこんどはfalseと表示される。これは==が物理的等価(physical equality)で判断するためだ。次のようにすると両方ともtrueと表示される。

# let a = 1;;
val a : int = 1
# let b = 1;;
val b : int = 1
# if a=b then print_endline "true" else print_endline "false";;
true
- : unit = ()
# let c = "a";;
val c : string = "a"
# let d = "a";;
val d : string = "a"
# if c=d then print_endline "true" else print_endline "false";;
true
- : unit = ()

つまりmutable(変更可能)で定義されたものは物理的等価と構造的等価が異なるので==と=の結果が異なるものになる(らしい)。mutableというのが良く分からない人はとりあえず==は使わないと覚えていても問題ないと思う。

=の否定は<>。==の否定は!=になる。これらの比較演算子を試してみよう。次のような関数を用意する。

# let func1 a b c = if a b c then print_endline "true" else print_endline "false";;
val func1 : ('a -> 'b -> bool) -> 'a -> 'b -> unit = <fun>

演算子も結局は関数なので次のように関数の引数にできる。

# func1 (>) 2 3;;
false
- : unit = ()
# func1 (>) 3 2;;
true
- : unit = ()
# func1 (>=) 4 2;;
true
- : unit = ()
# func1 (>=) 3 3;;
true
- : unit = ()
# func1 (<) 3 2;;
false
- : unit = ()
# func1 (=) 1 2;;
false
- : unit = ()
# func1 (=) 1 1;;
true
- : unit = ()
# func1 (<>) 1 2;;
true
- : unit = ()
# func1 (<>) 1 1;;
false
- : unit = ()

ちなみに関数を調べるときは例えば

# func1;;
- : ('a -> 'b -> bool) -> 'a -> 'b -> unit = <fun>

とするととる型を調べられる。上の例はまだ決まっていない型'a , 'bからbool値を返す関数と'aと'bという型をとる関数という意味だ。この場合'aと'bは基本的に何でも良い。

for文

次はforループだ。これはCなどと比べても単純なものになっている。1ずつ増えるものと1ずつ減るものの2種類がある。例を挙げる。

# for i=1 to 5 do print_int i; print_newline(); done;;
1
2
3
4
5
- : unit = ()
# for i=5 downto 0 do print_int i; print_newline(); done;;
5
4
3
2
1
0
- : unit = ()

この例で分かるように初期設定された値は一ずつ増えるか減るかの2種類で増分の指定などはできない。

while文

増分を指定したループなどをしたいときはすこし汚くなるが、while文を使う。また以外なことにOCamlにはbreak文がない。これは例外処理を利用する。OCamlでは例外処理を、Javaなどとは違い普通の処理の流れを制御する目的で使う。これは例外処理の説明のところで例を挙げた。whileループの使い方は次のような感じ。

# let r = ref 0 in while !r<10 do print_int !r; print_newline(); r:=!r+2; done;;
0
2
4
6
8
- : unit = ()

このようにすると増分が指定できる。

パターンマッチ

OCamlの基本的な文法はこのパターンマッチで最後になる。バリアント型などを利用したりするのには不可欠な操作でもあり、Cなどにはない機能でもある。

パターンマッチの基本的な構文は次の通り。

match expr with 
 | p1 -> expr1 
 | p2 -> expr2
 ...
 | pn -> exprn

exprがp1のときexpr1が実行されp2のときexpr2が実行される。また最初の|は省略できる。例を挙げる。

(* pattern_test1.ml *)
let func1 a =
 match a with
 |1 -> print_endline "a"
 |_ -> print_endline "b";;
let func2 a =
 match a with
  1 -> print_endline "a";
 |_ -> print_endline "b";;
let _ =
 func1 1;
 func1 2;
 func2 1;
 func2 2;;
[radio@taka sub2]$ ocaml pattern_test1.ml
a
b
a
b

_はワイルドカードでどんなものにもマッチする。

パターンマッチはfunctionキーワードと一緒に使うときには省略記法がある。これは一般には次のような形になる。

function | p1 -> expr1 
         | p2 -> expr2
         | p3 -> expr3
         ...
         | pn -> exprn

この例は次のように使う。

(* pattern_test2.ml *)
let func1 = function
 | 1 -> print_endline "a"
 | _ -> print_endline "b";;
let _ =
 func1 1;
 func1 2;;
[radio@taka sub2]$ ocaml pattern_test2.ml
a
b

などと使う。慣れないとわかりにくいので説明すると、functionは一引数をとる関数を返す関数だった。その一引数をパターンマッチさせている。のだが、個人的には分かりにくいと思う。このように宣言したとき、func1は引数をひとつ持つ。が定義の中身でこの引数は出てこない。引数の名前を設定しなくても良いので慣れれば便利そうだが最初のうちはわけが分からない。これは次のように書いても同じ。

(* pattern_test3.ml *)
let func1 = function a ->
 match a with
 | 1 -> print_endline "a"
 | _ -> print_endline "b";;
let _ =
 func1 1;
 func1 2;;
[radio@taka sub2]$ ocaml pattern_test3.ml
a
b

これもlet func1までみると引数をとらないように見えて気持悪い。次のように書くのが一番見やすいと思うが、状況にもよるので、適切なものを選びたい。

(* pattern_test4.ml *)
let func1 a =
 match a with
 | 1 -> print_endline "a"
 | _ -> print_endline "b";;
let _ =
 func1 1;
 func1 2;;
[radio@taka sub2]$ ocaml pattern_test4.ml
a
b

パターンにマッチしたときの処理を複数にしたいときは;を使うことができる。例を挙げる。

(* pattern_test5.ml *)
let func1 a =
 match a with
 | 1 -> print_endline "a";
        print_endline "a";
 | _ -> print_endline "b";
        print_endline "b";;
let _ =
 func1 1;
 func1 2;;
[radio@taka sub2]$ ocaml pattern_test5.ml
a
a
b
b

複数の処理を記述したときの最後のセミコロンはなくても良い。次の例は正しく動く。

(* pattern_test6.ml *)
let func1 a =
 match a with
 | 1 -> print_endline "a";
        print_endline "a"
 | _ -> print_endline "b";
        print_endline "b";;
let _ =
 func1 1;
 func1 2;;
[radio@taka sub2]$ ocaml pattern_test6.ml
a
a
b
b

またパターンマッチはネストすることもできる。このとき処理の範囲を明確にするためbeginを使う。例を挙げる。

(* pattern_test7.ml *)
let func1 a b =
 match a with
 | 1 ->
     begin
      match b with
      | 1 -> print_endline "1-1"
      | _ -> print_endline "1-2"
     end
 | _ ->
     begin
      match b with
      | 1 -> print_endline "2-1"
      | _ -> print_endline "2-2"
     end;;
let _ =
 func1 1 1;
 func1 1 2;
 func1 2 1;
 func1 2 2;;
[radio@taka sub2]$ ocaml pattern_test7.ml
1-1
1-2
2-1
2-2

beginはかっこでも代用可。つまり次の例は上の例と等価。

(* pattern_test8.ml *)
let func1 a b =
 match a with
 | 1 ->
     (
      match b with
      | 1 -> print_endline "1-1"
      | _ -> print_endline "1-2"
     )
 | _ ->
     (
      match b with
      | 1 -> print_endline "2-1"
      | _ -> print_endline "2-2"
     );;
let _ =
 func1 1 1;
 func1 1 2;
 func1 2 1;
 func1 2 2;;
[radio@taka sub2]$ ocaml pattern_test8.ml
1-1
1-2
2-1
2-2

また当然パターンマッチはint型のみに限らない。上の例はネストさせないでも書くことが出来る。

(* pattern_test9.ml *)
let func1 a b =
 match (a,b) with
 | (1,1) -> print_endline "1-1"
 | (1,_) -> print_endline "1-2"
 | (_,1) -> print_endline "2-1"
 | _     -> print_endline "2-2";;
let _ =
 func1 1 1;
 func1 1 2;
 func1 2 1;
 func1 2 2;;
[radio@taka sub2]$ ocaml pattern_test9.ml
1-1
1-2
2-1
2-2

次にパターンマッチで使うキーワードasとwhenの使い方について説明する。それほど気にすることもないと思うが、存在くらいは知っていた方が良いだろう。簡単にだがふれる。asは例えば次のように使う。

(* pattern_test10.ml *)
let func1 a =
 match a with
 | "pattern_test10" as test -> print_endline test
 | _ -> print_endline "end";;
let _ =
 func1 "pattern_test10";
 func1 "test";;
[radio@taka sub2]$ ocaml pattern_test10.ml
pattern_test10
end

つまりasはパターンマッチにおいて条件節の変数にエイリアスのようなものを付けることが出来る。このスコープは処理部分のみで、次の例はエラー。

(* pattern_test11.ml *)
let func1 a =
 match a with
 | "pattern_test11" as test -> print_endline test
 | _ -> print_endline test;;
let _ =
 func1 "pattern_test11";
 func1 "test";;
[radio@taka sub2]$ ocaml pattern_test11.ml
File "pattern_test11.ml", line 5, characters 22-26:
Unbound value test

これだけだといまいち利点が分からないかも知れないが、バリアント型を使ったりするパターンマッチではコンストラクタ名をいちいち繰り返すのは見にくいので便利なこともある(らしい)。

次にwhenの使い方を見てみよう。これはある条件を付加できるもので2重の条件分岐を行いたいときに使う。

(* pattern_test12.ml *)
let func1 a b =
 match a with
 | 1 when b=1 -> print_endline "1-1"
 | 1 -> print_endline "1-2"
 | _ when b=1 -> print_endline "2-1"
 | _ -> print_endline "2-2";;
let _ =
 func1 1 1;
 func1 1 2;
 func1 2 1;
 func1 2 2;;
[radio@taka sub2]$ ocaml pattern_test12.ml
1-1
1-2
2-1
2-2

この例は結局以前のパターンマッチの例と同じ処理を行う。aが1の時は、最初の条件に合致するが、when condのcondが真でないと最初の処理は実行されない。

パターンマッチの例としてリストのappendを取り上げる。まずはappendのソースを用意して、それをインタプリタに読み込む。

(* append.ml *)
let rec append_trace l =
 match l with
 | [] -> print_newline ()
 | a::b -> Printf.printf "%d," a; append_trace b;;
let rec append l1 l2 =
 append_trace l1;
 match l1 with
 | [] -> l2
 | x::rest -> x::(append rest l2);;
# #use "append.ml";;
val append_trace : int list -> unit = <fun>
val append : int list -> int list -> int list = <fun>
# append [1;2;3] [2;3];;
1,2,3,
2,3,
3,
 
- : int list = [1; 2; 3; 2; 3]
# append [21;23;12] [4;5;6];;
21,23,12,
23,12,
12,
 
- : int list = [21; 23; 12; 4; 5; 6]

l1が小さくなっていく様子が確認できる。

次はバリアントのパターンマッチの例として2分木を取り上げる。これもソースを用意し、インタプリタから読み込む。

(* btree.ml *)
type 'a btree = Empty | Node of 'a * 'a btree * 'a btree;;
let rec member x btree =
 match btree with
 | Empty -> false
 | Node(y,left,right) ->
    if x=y then true else
     if x<y then member x left else member x right;;
let rec insert x btree =
 match btree with
 | Empty -> Node(x,Empty,Empty)
 | Node(y,left,right) ->
    if x<=y then Node(y,insert x left,right)
            else Node(y,left,insert x right);;
let rec get_btree l =
 match l with
 | [] -> Empty
 | hd::tl -> insert hd (get_btree tl);;

簡単に解説すればinsertは条件を満たすように木を下り、空のところまできたら自分の値を記憶させる。

# #use "btree.ml";;
type 'a btree = Empty | Node of 'a * 'a btree * 'a btree
val member : 'a -> 'a btree -> bool = <fun>
val insert : 'a -> 'a btree -> 'a btree = <fun>
val get_btree : 'a list -> 'a btree = <fun>
# let a = get_btree [4;5;6;7;1;2;3];;
val a : int btree =
  Node (3, Node (2, Node (1, Empty, Empty), Empty),
   Node (7, Node (6, Node (5, Node (4, Empty, Empty), Empty), Empty), Empty))
# member 2 a;;
- : bool = true
# member 4 a;;
- : bool = true
# member 10 a;;
- : bool = false
# member 20 a;;
- : bool = false
# let a = ref Empty;;
val a : '_a btree ref = {contents = Empty}
# a := insert 3 !a;;
- : unit = ()
# a := insert 5 !a;;
- : unit = ()
# member 3 !a;;
- : bool = true
# member 8 !a;;
- : bool = false

標準関数

型変換

OCamlは強く型付けされた言語なので、型が違うと全く違うものとして扱われる。だがfloatとintなど型が違っても比較したりするのが自然なものも存在する。このようなもののために型変換の関数が用意されている。代表的なものは次の通り。

floatはfloat_of_intと同じ。これらの関数を実際に使ってみる。

# float_of_int 2;;
- : float = 2.
# int_of_float 2.4;;
- : int = 2
# int_of_float 2.9;;
- : int = 2
# float 2;;
- : float = 2.
# float_of_int 4;;
- : float = 4.
# int_of_char 'a';;
- : int = 97
# char_of_int 100;;
- : char = 'd'
# string_of_bool true;;
- : string = "true"
# bool_of_string "false";;
- : bool = false
# string_of_int 1234;;
- : string = "1234"
# int_of_string "421";;
- : int = 421
# float_of_string "123.5";;
- : float = 123.5
# float_of_string "0.9e3";;
- : float = 900.
# string_of_float 1.23;;
- : string = "1.23"
# string_of_float 0.9e2;;
- : string = "90."
# Array.to_list [|1;2;3|];;
- : int list = [1; 2; 3]
# Array.of_list [1;2;3];;
- : int array = [|1; 2; 3|]

入出力

OCamlには入出力を行うための方法がいくつかある。大きく分けると標準出力、標準エラー出力、標準入力に対する関数を利用する。一番基本的な方法だ。次にチャネルを利用する方法。これは1番目よりも一般的でファイルに書き込んだりすることもできる。3番目にはほとんど使わないかも知れないが、Unixモジュールでシステムコールとしてread,writeを呼び出すということもできる。またPrintfモジュールを利用し、C言語と同様のフォーマットで読み書きを行うというのもある。このうち、ここでは基本入出力関数を用いたものとチャネルを利用したものを紹介する。

基本入出力

まずは基本的な出力関数から。これらは標準出力に出力する関数群。

print_int,print_float,print_char,print_stringはそれぞれの型を表示する組み込み関数。print_endlineはprint_stringと同じだが最後に改行を加える。print_newlineは改行する関数。実際に試してみる。

# print_int 2;;
2- : unit = ()
# print_float 2.4;;
2.4- : unit = ()
# print_char 't';;
t- : unit = ()
# print_string "ad";;
ad- : unit = ()
# print_endline "ab";;
ab
- : unit = ()
# print_newline();;
 
- : unit = ()

次に標準エラー出力に出力する関数群。

これも試してみる。prerrはそのままにして置くとバッファにためられるので明示的にフラッシュをかける。

# prerr_int 2; flush_all();;
2- : unit = ()
# prerr_float 4.3; flush_all();;
4.3- : unit = ()
# prerr_char 't'; flush_all();;
t- : unit = ()
# prerr_string "ter"; flush_all();;
ter- : unit = ()
# prerr_endline "aew"; flush_all();;
aew
- : unit = ()
# prerr_newline(); flush_all();;
 
- : unit = ()

標準出力に対する関数と同じ。なおマニュアルによるとprint_endline,print_endline,prerr_endline,prerr_newlineは出力するだけでなくflushを行うようだ。flushをかけるチャネルを指定することも出来る。次のようにする。

# prerr_char 'a'; flush stderr;;
a- : unit = ()

flushは次の型をとる。

val flush : out_channel -> unit

つまり、stdout,stderrはデフォルトで定義されているout_channelにすぎない。よってchannelを操作する関数は基本的にすべてstdout,stderrにも適用可能である。

標準出力、標準エラー出力がデフォルトで定義されているout_channelであるように標準入力はデフォルトで定義されているin_channelに過ぎない。標準入力関数には次のものが用意されている。

これも試してみる。

# read_int();;
23
- : int = 23
# read_float();;
324.1
- : float = 324.1
# read_line ();;
dfds2
- : string = "dfds2"

特に説明は要らないだろうがとる型だけは注目してほしい。read_int,read_floatはint、floatを返すのでそのまま計算に使える。

# 1 + read_int ();;
23
- : int = 24
# 1.2 +. read_float ();;
23.3
- : float = 24.5
# "test" ^ read_line ();;
sample
- : string = "testsample"

チャネル

次にchannelの説明に移る。上でも述べたが、stdout,stderr,stdinはout_channelとin_channelなのでチャネルを使える関数はすべて標準入出力として利用できる。

# stdout;;
- : out_channel = <abstr>
# stdin;;
- : in_channel = <abstr>
# stderr;;
- : out_channel = <abstr>

まずは一番単純なテキストファイルの読み書きから始める。hogeというファイルを作り、そこに文字を書き込む。次のようにする。

# let out = open_out "hoge";;
val out : out_channel = <abstr>
# output_char out 'a';;
- : unit = ()
# output_string out "string";;
- : unit = ()
# close_out out;;
- : unit = ()

一度インタプリタを終了してhogeというファイルをエディタなどで確認してみるとastringという文字が書き込まれていることが確認できる。また注意して欲しいのはopen_outという命令はテキストファイルを書き込むためのチャネルを開く命令だが、すでにファイルが存在したときにはそれを空にする。つまり常に全く内容のないファイルを開くことになる。内容を付け足したいときなどは後述のopen_out_genを使う。

次にin_channelを使い。この内容を読み込む。次のようにする。

# let input = open_in "hoge";;
val input : in_channel = <abstr>
# input_char input;;
- : char = 'a'
# input_line input;;
- : string = "string"
# close_in input;;
- : unit = ()

名前を見れば想像つくだろうが、output_char、output_stringはそれぞれ文字と文字列をout_channelに書き込む命令。input_char,input_lineはそれぞれin_channelから文字1つを読み込む、in_channelから1行読み込む命令だ。これより詳しい入出力をしたいときはPrintf,Scanfモジュールを使う必要がある。これはcのprintfと同じようなformatが使える。以下にPrintfモジュールの使い方の例を挙げる。詳しい説明はmanualを見てほしい。

# let out = open_out "hoge";;
val out : out_channel = <abstr>
# Printf.fprintf out "test1\ntest2\n %d,%f\n" 10 10.2;;
- : unit = ()
# close_out out;;
- : unit = ()
# let input = open_in "hoge";;
val input : in_channel = <abstr>
# input_line input;;
- : string = "test1"
# input_line input;;
- : string = "test2"
# input_line input;;
- : string = " 10,10.200000"
# close_in input;;
- : unit = ()

ところで、上の例ではinput_lineをひたすら手作業で繰り返したが、これでは行数が分からないテキストデータを読むことはできない。任意の行を読むには例外を利用する。以下にテキストデータを1行ずつ読み込み、表示する関数の例を挙げる。

(* io_test1.ml *)
let get_text filename =
 let input = open_in filename in
  try
   while true do
    print_endline (input_line input)
   done
  with
   End_of_file -> ();;
# #use "io_test1.ml";;
val get_text : string -> unit = <fun>
# get_text "io_test1.ml";;
(* io_test1.ml *)
let get_text filename =
 let input = open_in filename in
  try
   while true do
    print_endline (input_line input)
   done
  with
   End_of_file -> ();;
 
- : unit = ()

open関数にはopen_out,open_inの他にもバイナリモードでファイルを読み書きするopen_out_bin,open_in_binという関数も用意されているが、ここではふれない。必要な人はManual Chapter 19を読んでほしい。ただoutput_value、input_valueを使ったバイナリの読み書きは後で簡単に紹介する。

前述したように、例えばテキストファイルに内容を追加したいときにはopen_out関数ではできない。このような少し詳しい指定をしたいときにopen_out_gen,open_in_gen関数が用意されている。これはopen_out,out_binを一般化したものだ。例えば,次の例はソース中のopen_out、open_out_binの定義だ。

let open_out name =
 open_out_gen [Open_wronly; Open_creat; Open_trunc; Open_text] 0O666 name
let open_out_bin =
  open_out_gen [Open_wronly; Open_creat; Open_trunc; Open_binary] 0O666 name

ちなみにopen_in,open_in_binの定義は

let open_in name =
 open_in_gen [Open_rdonly; Open_text] 0 name
let open_in_bin name =
 open_in_gen [Open_rdonly; Open_binary] 0 name

になる。

open_out_genとopen_in_genのとる型は次のようになっている。

open_out_gen:
- : open_flag list -> int -> string -> out_channel = <fun>
open_in_gen:
- : open_flag list -> int -> string -> in_channel = <fun>

見ての通り返す値が違うだけでとる値は同じだ。三番目のstring型の引数はopen_out,open_inと同じでファイル名になる。2番目のint型の引数はpermissionの指定だ。つまりUnix系のOSで、読み込み、書き込み、実行のそれぞれに対してユーザー、グループ、その他の設定を8進数の整数で指定するやつだ。Unix系のOSを知っている人なら分かると思う(補足すると、実際にはこの数字にさらにファイル作成マスクがとられるのでこの通りになるとは限らない)。良く分からないときはopen_in,open_outの設定をまねてopen_out_genのときは0O666,open_in_genのときは0にすれば良いだろう。他のユーザーに書き込み権限を与えたくないのなら0O644,読ませたくもないなら0O600とでもすればよい。言ったように、これは作成マスクによっては違う結果になるが、とりあえず試してみよう。

(* io_test2.ml *)
let out1 = open_out_gen [Open_wronly;Open_creat;Open_trunc;Open_text] 0O666 \
    "hoge1";;
let out2 = open_out_gen [Open_wronly;Open_creat;Open_trunc;Open_text] 0O644 \
    "hoge2";;
let out3 = open_out_gen [Open_wronly;Open_creat;Open_trunc;Open_text] 0O600 \
    "hoge3";;
[radio@taka sub1]$ ocaml io_test2.ml
[radio@taka sub1]$ ls -l
合計 4
-rw-rw-r--    1 radio    radio           0  9月 26 16:50 hoge1
-rw-r--r--    1 radio    radio           0  9月 26 16:50 hoge2
-rw-------    1 radio    radio           0  9月 26 16:50 hoge3
-rw-rw-r--    1 radio    radio         277  9月 26 16:49 io_test2.ml

次にopen_flag_listの説明をする。上の例でなんとなく想像つくだろうが、これはファイルを開くときのいろいろな属性を指定する。open_flagは次のものがありこれらを必要なだけリストにしてopen_out_gen,open_in_genに渡す。open_flagには以下のようなものがある。

type open_flag =
 | Open_rdonly : 読み込みだけ。
 | Open_wronly : 書き込みだけ。
 | Open_append : ファイルに内容を追加する。
 | Open_creat : ファイルが存在しなければ作る。
 | Open_trunc : もしファイルが存在していたらその内容を空にする。
 | Open_excl : もしファイルが存在していたらエラーを返す。
 | Open_binary : バイナリモードでファイルを開く。
 | Open_text : テキストモードでファイルを開く。
 | Open_nonblock : non-blockingモードでファイルを開く。

例としてファイルにテキストを追加してみよう。まずは単純にopen_outを使うと上書きしてしまうことを確かめる。

# let out = open_out "hoge";;
val out : out_channel = <abstr>
# output_string out "test1";;
- : unit = ()
# close_out out;;
- : unit = ()
# let out = open_out "hoge";;
val out : out_channel = <abstr>
# output_string out "test2";;
- : unit = ()
# close_out out;;
- : unit = ()
# let input = open_in "hoge";;
val input : in_channel = <abstr>
# input_line input;;
- : string = "test2"
# close_in input;;
- : unit = ()

test1という文字列が消されてしまっていることを確認できるはずだ。次にOpen_appendを付け加えて試してみる。

# let out = open_out_gen [Open_wronly;Open_append;Open_text] 0 "hoge";;
val out : out_channel = <abstr>
# output_string out "test3";;
- : unit = ()
# close_out out;;
- : unit = ()
# let input = open_in "hoge";;
val input : in_channel = <abstr>
# input_line input;;
- : string = "test2test3"
# close_in input;;
- : unit = ()

こんどはtest3という文字列が付け加えられているはずだ。

次にユーザー定義のデータ型などバイナリでファイルに書き込む方法をみる。バイナリで書き込むにはもちろんopen_out_binなどを使っても良いし、Marshalモジュールを利用するという手もあるのだが、output_valueとinput_value関数を利用すれば簡単にすむことも多い。例を挙げる。

# let out = open_out "hoge";;
val out : out_channel = <abstr>
# type complex = { re:int; im:int };;
type complex = { re : int; im : int; }
# output_value out {re=1;im=3};;
- : unit = ()
# output_value out "test";;
- : unit = ()
# close_out out;;
- : unit = ()
# let input = open_in "hoge";;
val input : in_channel = <abstr>
# (input_value input : complex);;
- : complex = {re = 1; im = 3}
# (input_value input : string);;
- : string = "test"
# close_in input;;
- : unit = ()

きちんと取り出せているはずだ。このようにoutput_valueとinput_valueを使うとバイナリデータが非常に簡単に扱えるが、input_valueを使うときにはユーザープログラムが明示的に型を指示してやらなくてはいけない。

配列

配列は次のような形で直接定義できる。

# let a = [|1;2;3;4|];;
val a : int array = [|1; 2; 3; 4|]

要素の型は何でも良いが、すべて等しい型でなくてはいけない。つぎのようなのはエラー。

# let a = [|1;2;4.3|];;
This expression has type float but is here used with type int

上のように直接作る他に関数で数を指定して作ることもできる。Arrayモジュールのmakeという関数を使う。

# Array.make 2 3;;
- : int array = [|3; 3|]
# Array.make 3 'r';;
- : char array = [|'r'; 'r'; 'r'|]

一つめの引数が配列の要素数。2つめの引数がその初期値。

makeと同じ働きをするものとしてcreateというものがある。

# Array.create 2 3;;
- : int array = [|3; 3|]
# Array.create 3 'r';;
- : char array = [|'r'; 'r'; 'r'|]

が推奨されていないようなので、できるだけmakeを使うべきだ。

makeでは配列の初期値はすべて一定だったが、それぞれ異なる値を初期値にすることもできる。これにはinitという関数を使う。次のように使う。

# Array.init;;
- : int -> (int -> 'a) -> 'a array = <fun>
# Array.init 5 (fun i->i+1);;
- : int array = [|1; 2; 3; 4; 5|]

一個目のint型の引数が配列の要素数。2番目の引数の関数が初期値を決める関数だ。この関数は要素の添字を引数にとる。もう少し例を挙げる。

# Array.init 10 (fun i->i*i);;
- : int array = [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]
# Array.init 10 (fun i->char_of_int (i+60));;
- : char array = [|'<'; '='; '>'; '?'; '@'; 'A'; 'B'; 'C'; 'D'; 'E'|]
# Array.init 10 (fun i->Random.float 1000.0);;
- : float array =
[|582.751366306423506; 140.791689359313693; 330.020920806134;
  410.471260707692068; 315.248400694302063; 126.148465548233062;
  490.66814992105742; 440.231318386449061; 352.194067623989611;
  737.177321646603673|]

配列の内容を得るにはget関数を使うか、かっこで指定する。

# let a = Array.init 10 (fun i->i);;
val a : int array = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]
# Array.get a 2;;
- : int = 2
# Array.get a 3;;
- : int = 3
# a.(2);;
- : int = 2
# a.(3);;
- : int = 3

配列の要素の値を変更するにはsetを使うか、かっこと矢印で指定する。

# let a = Array.init 10 (fun i->i);;
val a : int array = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]
# Array.set a 4 14;;
- : unit = ()
# Array.set a 5 20;;
- : unit = ()
# a;;
- : int array = [|0; 1; 2; 3; 14; 20; 6; 7; 8; 9|]
# a.(9) <- 12;;
- : unit = ()
# a.(1) <- 49;;
- : unit = ()
# a;;
- : int array = [|0; 49; 2; 3; 14; 20; 6; 7; 8; 12|]

配列は値を取り出したり変更したりするものの他にもいくつかの関数が用意されている。以下にそれらの関数を挙げる。全てArrayモジュールの関数。

make_matrix
2次元配列をつくる。
length
配列の長さを返す。
append
2つの配列の要素をつなげた配列を新しく作り返す。
concat
配列のリストをうけとりそれらをすべてつなげる。
sub
配列の部分配列を返す。
copy
引数で指定した配列と同じ内容の配列を返す。
fill
引数で指定した範囲を3番目の引数の値で埋める。
to_list
配列をリストにしたものを返す。
of_list
リストを配列にしたものを返す。
sort,stable_sort,fast_sort
ソート関数。
iter,map,iteri,mapi
これらの関数は配列の要素すべてに対してある関数を適用させるもの。iterとmapの違いは適用させる関数の結果を配列にして返すかどうかであり、iterとiteri、mapとmapiの違いは適用させる関数の引数に配列の添字を含めるかどうかである。
fold_left,fold_right
いわゆる畳み込み関数。

以下にこれらの関数の使い方の具体例を示す。

make_matrixは最初の2引数でサイズを指定し、3つめの引数で行列の要素を指定する。

# Array.make_matrix;;
- : int -> int -> 'a -> 'a array array = <fun>
# let a = Array.make_matrix 2 3 'a';;
val a : char array array = [|[|'a'; 'a'; 'a'|]; [|'a'; 'a'; 'a'|]|]
# a.(1).(2) <- 'y';;
- : unit = ()
# a.(0).(1) <- 'r';;
- : unit = ()
# a;;
- : char array array = [|[|'a'; 'r'; 'a'|]; [|'a'; 'a'; 'y'|]|]

lengthは名前から想像できるように配列の長さを返す。

# Array.length;;
- : 'a array -> int = <fun>
# Array.make_matrix;;
- : int -> int -> 'a -> 'a array array = <fun>
# let a = [|1;2;3;4;5|];;
val a : int array = [|1; 2; 3; 4; 5|]
# Array.length a;;
- : int = 5

appendは引数の2つの配列をつなげたものを返す。

# Array.append;;
- : 'a array -> 'a array -> 'a array = <fun>
# Array.append [|1;2;3;4|] [|5;6;7|];;
- : int array = [|1; 2; 3; 4; 5; 6; 7|]

concatは配列のリストをとり、それらすべてをつなげたものを返す。

# Array.concat;;
- : 'a array list -> 'a array = <fun>
# Array.concat [ [|1;2;3|]; [|4;5;6|]; [|7;8;9|] ];;
- : int array = [|1; 2; 3; 4; 5; 6; 7; 8; 9|]

subは引数で指定された部分の部分配列を返す。

# Array.sub;;
- : 'a array -> int -> int -> 'a array = <fun>
# Array.sub [|1;2;3;4;5;6;7;8;9|] 2 4;;
- : int array = [|3; 4; 5; 6|]

copyは引数で指定した配列と同じ内容の配列を返す。

# Array.copy;;
- : 'a array -> 'a array = <fun>
# let a = [|1;2;3;4;5|];;
val a : int array = [|1; 2; 3; 4; 5|]
# let b = Array.copy a;;
val b : int array = [|1; 2; 3; 4; 5|]
# b.(2) <- 9;;
- : unit = ()
# a;;
- : int array = [|1; 2; 3; 4; 5|]
# b;;
- : int array = [|1; 2; 9; 4; 5|]
# a.(3) <- 12;;
- : unit = ()
# a;;
- : int array = [|1; 2; 3; 12; 5|]
# b;;
- : int array = [|1; 2; 9; 4; 5|]

fillは引数で指定した範囲を三番目の引数の値で埋める。

# Array.fill;;
- : 'a array -> int -> int -> 'a -> unit = <fun>
# let a = [|1;2;3;4;5;6;7|];;
val a : int array = [|1; 2; 3; 4; 5; 6; 7|]
# Array.fill a 2 4 19;;
- : unit = ()
# a;;
- : int array = [|1; 2; 19; 19; 19; 19; 7|]

to_listは配列の引数をリストにしたものを返す。

# Array.to_list;;
- : 'a array -> 'a list = <fun>
# Array.to_list [|1;2;3;4;5|];;
- : int list = [1; 2; 3; 4; 5]

of_listはリストの引数を配列にしたものを返す。

# Array.of_list;;
- : 'a list -> 'a array = <fun>
# Array.of_list [1;2;3;4;5;6];;
- : int array = [|1; 2; 3; 4; 5; 6|]

sort,stable_sort,fast_sortは基本的には全部同じ。最初の引数で比較関数を定義してソートする。

# Array.sort;;
- : ('a -> 'a -> int) -> 'a array -> unit = <fun>
# Array.stable_sort;;
- : ('a -> 'a -> int) -> 'a array -> unit = <fun>
# Array.fast_sort;;
- : ('a -> 'a -> int) -> 'a array -> unit = <fun>
# let a = Array.init 10 (fun i-> Random.int 100);;
val a : int array = [|80; 45; 18; 19; 28; 49; 86; 18; 64; 45|]
# Array.sort (fun a b-> if a>=b then 1 else ~-1) a;;
- : unit = ()
# a;;
- : int array = [|18; 18; 19; 28; 45; 45; 49; 64; 80; 86|]
# let a = Array.init 10 (fun i->Random.int 100);;
val a : int array = [|82; 54; 18; 50; 12; 41; 64; 85; 99; 92|]
# Array.sort (fun a b-> if a>=b then ~-1 else 1) a;;
- : unit = ()
# a;;
- : int array = [|99; 92; 85; 82; 64; 54; 50; 41; 18; 12|]

iter,map,iteri,mapiは配列の要素すべてに対してある関数を適用させるもの。

# Array.map;;
- : ('a -> 'b) -> 'a array -> 'b array = <fun>
# Array.mapi;;
- : (int -> 'a -> 'b) -> 'a array -> 'b array = <fun>
# Array.iter;;
- : ('a -> unit) -> 'a array -> unit = <fun>
# Array.iteri;;
- : (int -> 'a -> unit) -> 'a array -> unit = <fun>
# let a = [|1;2;3;4;5;6|];;
val a : int array = [|1; 2; 3; 4; 5; 6|]
# let b = Array.init 6 (fun i->float_of_int i);;
val b : float array = [|0.; 1.; 2.; 3.; 4.; 5.|]
# Array.map (fun i->i+2) a;;
- : int array = [|3; 4; 5; 6; 7; 8|]
# Array.map (fun i->sin i) b;;
- : float array =
[|0.; 0.841470984807896505; 0.909297426825681709; 0.141120008059867214;
  -0.756802495307928202; -0.958924274663138454|]
# Array.mapi (fun i x -> Printf.printf "[%d]:%f\n" i (sin x);sin x) b;;
[0]:0.000000
[1]:0.841471
[2]:0.909297
[3]:0.141120
[4]:-0.756802
[5]:-0.958924
- : float array =
[|0.; 0.841470984807896505; 0.909297426825681709; 0.141120008059867214;
  -0.756802495307928202; -0.958924274663138454|]
# Array.iter (fun i -> Printf.printf "%d\n" i) a;;
1
2
3
4
5
6
- : unit = ()
# Array.iteri (fun i x -> Printf.printf "array[%d]=%d\n" i x) a;;
array[0]=1
array[1]=2
array[2]=3
array[3]=4
array[4]=5
array[5]=6
- : unit = ()

fold_left,fold_rightは畳み込み関数。map以上に難しい。ので、簡単に触れるだけ。覚えておくとなにかの役に立つかもしれない。

# Array.fold_left;;
- : ('a -> 'b -> 'a) -> 'a -> 'b array -> 'a = <fun>
# Array.fold_right;;
- : ('a -> 'b -> 'b) -> 'a array -> 'b -> 'b = <fun>
# Array.fold_left (+) 0 [|1;2;3;4;5;6|];;
- : int = 21
# Array.fold_right (+) [|1;2;3;4;5;6|] 0;;
- : int = 21

リスト

リストは次のような形で直接定義できる。

# let a = [1;2;3;4];;
val a : int list = [1; 2; 3; 4]

リストに要素を付け加えるには

# 0::[1;2;3;4];;
- : int list = [0; 1; 2; 3; 4]

などとする。2つのリストをひとつにするには

# [1;2;3;4]@[5;6;7;8];;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8]

とする。

リスト操作の代表的なものとして以下のものがある。

length
リストの長さを返す。
hd
リストの先頭要素を返す。
tl
リストの先頭要素以外を返す。
rev
逆順のリストを返す。
nth
リストの引数の場所の要素を返す。
concat
リストのリストをとってそれぞれをつなげる。
iter,map,iter2,map2
配列のものと基本的な役割は同じ。関数をリストの各要素に適用する。iter2,map2は2つのリストの要素をを引数にする関数をとる。
fold_left,fold_right,fold_left2,fold_right2
配列のものと基本的な役割は同じ。
for_all,exists,for_all2,exists2
リストの要素それぞれに対して真偽を返す関数をとり、そのand、もしくはorを返す。
mem,memq
リストの要素に含まれるかをどうかを返す。memqは==で判定し、memは=で判定する。
find,find_all,partition
リストの各要素に対して条件を満たしているものを返す。
sort,stable_sort,fast_sort
配列のものとほとんど同じ。
merge
リストをマージする。マージされるリストは第一引数の比較関数でソートされていることを前提とする。

以下にこれらの関数の使い方の具体例を示す。

lengthは配列と同様で引数のリストの長さを返す。

# List.length;;
- : 'a list -> int = <fun>
# List.length [1;2;3;4;5];;
- : int = 5

hdはリストの先頭の要素を返す。

# List.hd;;
- : 'a list -> 'a = <fun>
# List.hd [3;4;5;6];;
- : int = 3

tlはリストの先頭以外の要素を返す。

# List.tl;;
- : 'a list -> 'a list = <fun>
# List.tl [4;5;6;7];;
- : int list = [5; 6; 7]

revは逆順のリストを返す。

# List.rev;;
- : 'a list -> 'a list = <fun>
# List.rev [3;4;5;6;7];;
- : int list = [7; 6; 5; 4; 3]

nthは指定されたリストの要素を返す。

# List.nth;;
- : 'a list -> int -> 'a = <fun>
# List.nth [0;1;2;3;4;5;6] 3;;
- : int = 3

concatはリストの要素のリストを引数に取り、それらをつなげたものを返す。

# List.concat;;
- : 'a list list -> 'a list = <fun>
# List.concat [ [1;2;3;4]; [5;6;7;8]; [9;0;1;2] ];;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 0; 1; 2]

iter,map,iter2,map2は基本的な役割は配列のものと同じ。関数をリストの要素全てに適用する。2がついているほうはリスト2つを引数としてとる。

# List.map;;
- : ('a -> 'b) -> 'a list -> 'b list = <fun>
# List.iter;;
- : ('a -> unit) -> 'a list -> unit = <fun>
# List.map2;;
- : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list = <fun>
# List.iter2;;
- : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit = <fun>
# let a = [1;2;3;4;5;6];;
val a : int list = [1; 2; 3; 4; 5; 6]
# let b = Array.to_list (Array.init 6 (fun i->float_of_int i));;
val b : float list = [0.; 1.; 2.; 3.; 4.; 5.]
# List.map (fun i->i+2) a;;
- : int list = [3; 4; 5; 6; 7; 8]
# List.map (fun i->sin i) b;;
- : float list =
[0.; 0.841470984807896505; 0.909297426825681709; 0.141120008059867214;
 -0.756802495307928202; -0.958924274663138454]
# List.iter (fun i-> Printf.printf "%d\n" i) a;;
1
2
3
4
5
6
- : unit = ()
# List.iter (fun i-> Printf.printf "%f\n" i) b;;
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
- : unit = ()
# let c = [4;5;6;7;8;9];;
val c : int list = [4; 5; 6; 7; 8; 9]
# let d =Array.to_list (Array.init 6 (fun i->float_of_int i));;
val d : float list = [0.; 1.; 2.; 3.; 4.; 5.]
# List.map2 (fun x y-> x+y) a c;;
- : int list = [5; 7; 9; 11; 13; 15]
# List.iter2 (fun x y -> Printf.printf "%f\n" (sqrt(x**y))) b d;;
1.000000
1.000000
2.000000
5.196152
16.000000
55.901699
- : unit = ()

fold_left,fold_rightは配列のものと同じ。fold_left2,fold_right2は2つのリストの引数をとる。

# List.fold_left;;
- : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>
# List.fold_right;;
- : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun>
# List.fold_left2;;
- : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a = <fun>
# List.fold_right2;;
- : ('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> 'c -> 'c = <fun>
# let a = [1;2;3;4;5;6];;
val a : int list = [1; 2; 3; 4; 5; 6]
# let b = [9;8;7;6;5;4];;
val b : int list = [9; 8; 7; 6; 5; 4]
# List.fold_left (+) 0 a;;
- : int = 21
# List.fold_right (+) a 0;;
- : int = 21
# List.fold_left2 (fun a b c->a+b+c) 0 a b;;
- : int = 60
# List.fold_right2 (fun a b c->a+b+c) a b 0;;
- : int = 60

for_all,exists,for_all2,exists2はリストの全ての要素に対して真偽を返す関数をとり、それのandもしくはorを返す。2がついているほうは2つのリストの要素を使い真偽を出す。

(* list_test1.ml *)

(* リストの要素が全て0以上であれば真を返す *)
let func1 l = List.for_all (fun a -> a>=0) l;;

(* リストの要素で0以上のものが存在すれば真を返す *)
let func2 l = List.exists (fun a -> a>=0) l;;

(* l1の要素が全てl2の要素より大きければ真を返す *)
let func3 l1 l2 = List.for_all2 (fun a b -> a>b) l1 l2;;

(* l1の要素の中でl2の要素より大きいものがあれば真を返す *)
let func4 l1 l2 = List.exists2 (fun a b -> a>b) l1 l2;;
# List.for_all;;
- : ('a -> bool) -> 'a list -> bool = <fun>
# List.exists;;
- : ('a -> bool) -> 'a list -> bool = <fun>
# List.for_all2;;
- : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool = <fun>
# List.exists2;;
- : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool = <fun>
# #use "list_test1.ml";;
val func1 : int list -> bool = <fun>
val func2 : int list -> bool = <fun>
val func3 : 'a list -> 'a list -> bool = <fun>
val func4 : 'a list -> 'a list -> bool = <fun>
# func1 [~-2;4];;
- : bool = false
# func1 [2;3];;
- : bool = true
# func2 [~-3;~-9];;
- : bool = false
# func2 [2;~-3];;
- : bool = true
# func3 [1;2;3;4] [0;2;0;0];;
- : bool = false
# func3 [2;3;6;7] [1;2;3;5];;
- : bool = true
# func4 [1;2;3;4] [0;3;4;5];;
- : bool = true
# func4 [2;3;6;7] [1;2;3;5];;
- : bool = true

mem,memqはリストの要素に含まれるかを判定する。memqは==で判定し、memは=で判定する。

# List.mem;;
- : 'a -> 'a list -> bool = <fun>
# List.memq;;
- : 'a -> 'a list -> bool = <fun>
# List.mem 2 [1;2;3];;
- : bool = true
# List.mem 0 [1;2;3];;
- : bool = false
# List.memq 2 [1;2;3];;
- : bool = true
# List.memq 0 [1;2;3];;
- : bool = false
# List.mem "a" ["a";"b";"c"];;
- : bool = true
# List.memq "a" ["a";"b";"c"];;
- : bool = false

find,find_all,partitionはリストの要素で条件を満たしているものを返す。

(* list_test2.ml *)

(* リストの要素で5より小さい最初の要素を返す *)
let func1 l = List.find (fun a -> 5>a) l;;

(* リストの要素で5より小さいものをリストにして返す *)
let func2 l = List.find_all (fun a -> 5>a) l;;

(* リストの要素で5より小さいものと小さくないものをタプルにして返す *)
let func3 l = List.partition (fun a-> 5>a) l;;
# List.find;;
- : ('a -> bool) -> 'a list -> 'a = <fun>
# List.find_all;;
- : ('a -> bool) -> 'a list -> 'a list = <fun>
# List.partition;;
- : ('a -> bool) -> 'a list -> 'a list * 'a list = <fun>
# #use "list_test2.ml";;
val func1 : int list -> int = <fun>
val func2 : int list -> int list = <fun>
val func3 : int list -> int list * int list = <fun>
# func1 [3;7;4;3];;
- : int = 3
# func1 [6;7;8;1];;
- : int = 1
# func2 [4;6;5;7;8;2;4];;
- : int list = [4; 2; 4]
# func3 [4;6;7;2;3;9];;
- : int list * int list = ([4; 2; 3], [6; 7; 9])

sort,stable_sort,fast_sortは配列のものとほとんど同じ。比較関数を定義して使う。

# List.sort;;
- : ('a -> 'a -> int) -> 'a list -> 'a list = <fun>
# List.fast_sort;;
- : ('a -> 'a -> int) -> 'a list -> 'a list = <fun>
# List.stable_sort;;
- : ('a -> 'a -> int) -> 'a list -> 'a list = <fun>
# let a = Array.to_list (Array.init 6 (fun i->Random.int 50));;
val a : int list = [0; 17; 15; 7; 26; 10]
# List.sort (fun x y-> x-y) a;;
- : int list = [0; 7; 10; 15; 17; 26]
# let a = Array.to_list (Array.init 6 (fun i->Random.int 50));;
val a : int list = [32; 24; 42; 20; 25; 33]
# List.sort (fun x y-> y-x) a;;
- : int list = [42; 33; 32; 25; 24; 20]

mergeはリストをマージする。マージされるリストは第一引数の比較関数でソートされていることを前提とする。

# List.merge;;
- : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list = <fun>
# let comp a b = a-b;;
val comp : int -> int -> int = <fun>
# let func1 l = List.sort comp l;;
val func1 : int list -> int list = <fun>
# List.merge comp (func1 [1;3;1;~-4]) (func1 [6;2;34;3]);;
- : int list = [-4; 1; 1; 2; 3; 3; 6; 34]

Unixモジュール

Unixモジュールの機能の一部を簡単に紹介する。

ラベル

unixモジュールの紹介に入る前にラベルについて説明する。ラベルは関数の引数に名前をつけることが出来る機能でプログラムを読みやすくしたり、一部の引数だけを指定すればよいようにしたいときに使う。まず単純なラベル付き引数の例を挙げる。

# let f ~x ~y = x+y;;
val f : x:int -> y:int -> int = <fun>

これは次のいずれでも呼び出せる。

# f 2 3;;
- : int = 5
# f ~x:2 ~y:3;;
- : int = 5
# let x=2 and y=3 in f ~x ~y;;
- : int = 5

ラベルのあとに変数名を指定しなければラベルと同じ変数名になる。変数名を指定するには次のようにする。

# let f ~x:x1 ~y:y1 = x1+y1;;
val f : x:int -> y:int -> int = <fun>

これも次のいずれでも呼び出せる。

# f 2 3;;
- : int = 5
# f ~x:2 ~y:3;;
- : int = 5
# let x=2 and y=3 in f ~x ~y;;
- : int = 5

チルダを使ったラベルは省略できないが、次のように?を使ったラベルは省略できる(下の例にあるように呼ぶときは明示的に呼ばなくてはいけない)。

# let f ?(x=1) y = x+y;;
val f : ?x:int -> int -> int = <fun>
# f 2;;
- : int = 3
# f 4;;
- : int = 5
# f 3 2;;
Expecting function has type ?x:int -> int
This argument cannot be applied without label
# f ~x:3 2;;
- : int = 5
# f ~x:4 3;;
- : int = 7

オプションラベルと関数名を異なったものにするには次のようにする。

# let f ?x_point:(x=3) y = x+y;;
val f : ?x_point:int -> int -> int = <fun>
# f 2;;
- : int = 5
# f 4;;
- : int = 7
# f ~x_point:4 2;;
- : int = 6

ところで全ての値をこのようにオプション引数にすることは出来るだろうか?次のように打ち込んでみてほしい。

# let f ?(x=1) ?(y=1) = x + y;;
Warning: This optional argument cannot be erased
val f : ?x:int -> ?y:int -> int = <fun>

一応関数が定義されるが警告が出たはずだ。これを避けるには最後にunit型を付け加える。

# let f ?(x=1) ?(y=1) () = x+y;;
val f : ?x:int -> ?y:int -> unit -> int = <fun>
# f ~x:2 ~y:4 ();;
- : int = 6
# f ~x:2 ();;
- : int = 3
# f ~y:4 ();;
- : int = 5
# f ();;
- : int = 2

関数の引数が省略できているのが分かる。

unixモジュールには機能的には同等だが、ラベル付きのものとラベルなしのものが用意されている。使うには

#load "unix.cma";;
module Unix = UnixLabels;;

とするようだが、意味も良く分からないし面倒くさいのでここでは普通のモジュールを使う。

ソケット

socketを使った通信を試してみよう。ほとんどC言語と同じだ。

まずドメイン名からIPアドレスを表示する関数を作ってみよう。名前からホスト名などの情報を表示するにはgethostbynameを使うが、まずはこの関数をインタプリタで試してみる。

# #load "unix.cma";;
# let func1 name = Unix.gethostbyname name;;
val func1 : string -> Unix.host_entry = <fun>
# func1 "google.co.jp";;
- : Unix.host_entry =
{Unix.h_name = "google.co.jp"; Unix.h_aliases = [||];
 Unix.h_addrtype = Unix.PF_INET;
 Unix.h_addr_list = [|<abstr>; <abstr>; <abstr>|]}

この中でh_addr_listがIPアドレスを格納している。google.co.jpは3つのIPアドレスを持っていることが分かる。これを表示するにはstring_of_inet_addrを使う。これらをまとめると次のような感じのプログラムが出来る。

(* unix_test2.ml *)
let get_ip name =
 let host_entry = Unix.gethostbyname name in
  Array.map Unix.string_of_inet_addr host_entry.Unix.h_addr_list;;
# #load "unix.cma";;
# #use "unix_test2.ml";;
val get_ip : string -> string array = <fun>
# get_ip "google.co.jp";;
- : string array = [|"216.239.59.104"; "216.239.39.104"; "216.239.57.104"|]

これでドメイン名からIPアドレスをリストにして返す関数が出来た。ここで注意してほしいのはh_addr_listの前にUnixと付けること忘れないようにすることだ。openを使わない限り、これは必須になる。unixモジュールとの名前の一致も少ないだろうからopenを使ってしまっても良いかもしれない。

一応通信が出来ることが出来ることを確かめたのでsocket関連の型や関数をまとめてみたい。

type inet_addr
IPアドレスを表す抽象型。inet_addr_of_string,string_of_inet_addrの2つの関数で文字列と相互に変換可能。
type socket_domain = PF_UNIX | PF_INET
C言語でソケット通信のプログラムを書ける人は知っているだろうが、PF_UNIXは一台のマシンで通信を行う(X Windowのような感じ)ためのもので、PF_INETは普通のネットワークのためのもの。
type socket_type = SOCK_STREAM | SOCK_DGRAM | SOCK_RAW | SOCK_SEQPACKET
これもCと同じ。自分はSOCK_STREAMしか使ったことがない。
type sockaddr = ADDR_UNIX of string | ADDR_INET of (inet_addr * int)
ADDR_UNIXというのがUNIXドメインの通信のためのもの。文字列はファイル名。ADDR_INETというのがインターネットなどの通信を行うためのものでIPアドレスとポート番号のタプル。ソケットはこのどちらかとバインドする。
val socket: socket_domain -> socket_type -> int -> file_descr
C言語と同じ。最後のintは普通0を指定する。socket_domainは一台の中で通信させたいならPF_UNIX,違うマシンと通信したいならPF_INET。socket_typeはSOCK_STREAM,SOCK_DGRAMなどだがSOCK_STREAMが手軽。file_descrはC言語ではただのintだがOCamlでは特別な型として定義されている。次の関数でchannelと相互に変換可能。
val in_channel_of_descr: file_descr -> Pervasives.in_channel
val out_channel_of_descr: file_descr -> Pervasives.out_channel
val descr_of_in_channel: Pervasives.in_channel -> file_descr
val descr_of_out_channel: Pervasives.out_channel -> file_descr
val accept: file_descr -> file_descr * sockaddr
返り値はクライアント側と接続しているsocketのfile_descrとクライアントのアドレス(インターネットのときはIPアドレスとポート番号)。
val bind: file_descr -> sockaddr -> unit
Cと同じ。
val connect: file_descr -> sockaddr -> unit
Cと同じ。
val listen: file_descr -> int -> unit
Cと同じ。

他の定義に関してはManualを読んでほしい。

ソケット通信の簡単な例としてHTTP1.0とHTTP1.1を使った通信のプログラムを作ってみる。

(* unix_test3.ml *)
let connect host port =
 let addr = Unix.gethostbyname host in
 let s = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  Unix.connect s (Unix.ADDR_INET(addr.Unix.h_addr_list.(0),port));
  s;;
let get str sock =
 let a = Unix.in_channel_of_descr sock in
  ignore(Unix.write sock str 0 (String.length str));
  try
   while true do
    print_endline (input_line a);
   done
  with
   End_of_file -> close_in a;;
let get_html10 name =
 let str = "GET /index.html HTTP/1.0\r\n\r\n" in
 let sock = connect name 80 in
  get str sock;;
let get_html11 name =
 let str1 = "GET /index.html HTTP/1.1\n" in
 let str2 = "Host:"^name^"\n" in
 let str3 = "Connection:close\n\n" in
 let sock = connect name 80 in
  get (str1^str2^str3) sock;;
# #load "unix.cma";;
# #use "unix_test3.ml";;
val connect : string -> int -> Unix.file_descr = <fun>
val get : string -> Unix.file_descr -> unit = <fun>
val get_html10 : string -> unit = <fun>
val get_html11 : string -> unit = <fun>

上に続いて、下のように関数を呼び出してgoogleに接続する。結果が長いのでget_html11のほうは省略する。

# get_html10 "www.google.co.jp";;
HTTP/1.0 302 Found
Location: \
    http://www.google.co.jp/cxfer?c=PREF%3D:TM%3D1096208163:S%3DhHFSb_2us9lNaV2A \
Set-Cookie: \
    PREF=ID=1069f0a827444391:CR=1:TM=1096208163:LM=1096208163:S=Hm9c-udJB71YTjIO; \
    expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; \
    domain=.google.com
Content-Type: text/html
Server: GWS/2.1
Content-Length: 206
Date: Sun, 26 Sep 2004 14:16:03 GMT
Connection: Keep-Alive
 
<HTML><HEAD><TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A \
    HREF="http://www.google.co.jp/cxfer?c=PREF%3D:TM%3D1096208163:S%3DhHFSb_2us9lNaV2A">here</A>. \
</BODY></HTML>
- : unit = ()

もうひとつソケット通信を使った簡単な例としてrubyプログラムとUNIXドメインを使って通信させてみる。

rubyプログラムは次のような感じ。

# unix_test4.rb
require 'socket'
$path = "/tmp/socket"
sock = UNIXServer.open($path)
s1 = sock.accept
p s1.recvfrom(100)
s1.close

ocamlプログラムは次のような感じ。

(* unix_test4.ml *)
let pfunix_test name test_str=
 let sockfd = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
  Unix.connect sockfd (Unix.ADDR_UNIX name);
  ignore(Unix.write sockfd test_str 0 (String.length test_str));
  Unix.close sockfd;
  Unix.system "rm /tmp/socket";;

このプログラムの使い方はまずocamlが起動しているのとは別のシェルから次のように打ち込む。

[radio@taka sub2]$ ruby unix_test4.rb

そのあとにocamlインタプリタから

# #load "unix.cma";;
# #use "unix_test4.ml";;
val pfunix_test : string -> string -> Unix.process_status = <fun>
# pfunix_test "/tmp/socket" "test_string";;
- : Unix.process_status = Unix.WEXITED 0

渡す文字列は任意だが、ファイル名は決めうちになっているので、最初(rubyプログラムを起動させる前)に/tmp/socketというファイルがあるとエラーになる。そうするとrubyを起動させておいたシェルに文字列が渡されているのが分かる。

[radio@taka sub2]$ ruby unix_test4.rb
["test_string", ["AF_UNIX", ""]]

パイプ

Unixモジュールの最後の例としてパイプを使ってrubyプログラムの結果をocamlプログラムで受け取るプログラムを作ってみよう。次のようなrubyプログラムをインタプリタが起動しているディレクトリに用意する。これをunix_test5.rbとする。

# unix_test5.rb
print "Hello\n"
print "Hello\n"
print "Hello\n"

見て通り3行にわたってHelloと表示するだけのプログラムだ。これをstring型のリストとして受け取る関数を考える。

(* unix_test5.ml *)
let sub_func a return =
 try 
  while true do 
    return := (input_line a)::!return;
  done;
 with 
  End_of_file -> ();;

let pipe_test command =
  let std_in,std_out = Unix.pipe () in
  let rfd,wfd = Unix.pipe () in
  let return = ref [] in
    Unix.dup2 Unix.stdin std_in;
    Unix.dup2 Unix.stdout std_out;
    if Unix.fork () = 0 then
      begin
        Unix.dup2 wfd Unix.stdout;
	Unix.close wfd;
	Unix.close rfd;
	ignore(Unix.system command);
      end
    else 
      begin
        Unix.dup2 rfd Unix.stdin;
	Unix.close rfd;
	Unix.close wfd;
	let a = Unix.in_channel_of_descr Unix.stdin in
	  sub_func a return;
      end;
    Unix.dup2 std_in Unix.stdin;
    Unix.dup2 std_out Unix.stdout;
    Unix.close std_in;
    Unix.close std_out;
    !return;;

C言語に詳しい人ならもっときれいにできるのだろうが次のようにすると一応結果を受け取れていることが分かる。

# #load "unix.cma";;
# #use "unix_test5.ml";;
val sub_func : in_channel -> string list ref -> unit = <fun>
val pipe_test : string -> string list = <fun>
# pipe_test "ruby unix_test5.rb";;
- : string list = ["Hello"; "Hello"; "Hello"]
# - : string list = []

モジュールとオブジェクト

まだ書いてない。

ocamlmpi

ocamlmpiはインストールされているものとする。また以下の作業は全てnfsでシェアされているディレクトリで行う。ocamlmpiのコンパイルは

ocamlc -I +ocamlmpi mpi.cma hoge.ml -o hoge

実行は

mpirun -np 3

で出来るとする。ここではocamlmpi-1.01のソースでバグっぽかったのを一部変更したものを使用する。あと、communicatorとか発展的な話題はよく分かってないので適当に流して欲しい。

基本的な関数

まずは最も基本的な点対点の通信を見ていくが、その前にいくつかocamlmpiに定義されている変数や関数などを見ていく。当然全てmpiモジュール。ちなみにMPI_InitとかMPI_Finalizeはないようだ。自動的にやってくれるらしい。

type communicator 
type rank = int
val comm_world : communicator
external comm_size : communicator -> int = "caml_mpi_comm_size"
external comm_rank : communicator -> rank = "caml_mpi_comm_rank"
val barrier : communicator -> unit
external wtime : unit -> float = "coml_mpi_wtime"

MPIを知っている人は想像つくだろうが、とりあえず以下に説明する。

type communicator
communicatorのタイプ。communicatorとはノードのグループ。つまりデータを交換できるプロセスの集まり。
type rank
ノードのランク。あるcommunicatorに属しているノードは0から順に正の整数値が割り当てられ、これをそのノードのランクという。
val comm_world
グローバルなcommunicator。デフォルトでは全てのノードが参加している。
external comm_size
communicatorを引数に取り、そのcommunicatorの属しているノードの数を返す関数。
external comm_rank
この関数を呼んだノードの与えられたcommunicatorにおけるランク数を返す関数。
val barrier
与えられたcommunicatorの全ノードで同期をとる。
external wtime
プログラム実行から数えたclock時間を返す関数。

Point-to-point communication

まずは基本的な点対点の通信を試してみる。点対点通信では送るデータを識別するのにタグという特別な整数値を使う。タグはocamlmpiでは次のように定義されている

type tag = int

しかし全ての整数値が使えるわけではなく、タグは0から32767の間の整数値に限られる。このタグを用いて実際にメッセージをやりとりするには次のようなsend,receiveという関数を使う。

val send : 'a -> rank -> tag -> communicator
val receive : rank -> tag -> communicator -> 'a

これを使ってプロセス0からプロセス1に"Hello World"という文字列を送ってみる。

(* mpi_test1.ml *)
open Mpi
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send "Hello,World" 1 0 comm_world;
 if myrank=1 then print_endline (receive 0 0 comm_world)
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test1.ml -o mpi_test1
[radio@taka sub1]$ mpirun -np 3 mpi_test1
Hello,World

これをCで書くと多分下のような感じになる。比べてもらえると分かると思うが、OCamlの方が短く簡単にかける。

/* mpi_test1.c */
#include <stdio.h>
#include <string.h>
#include <mpi.h>
int main(int argc, char **argv){
  int myid, numprocs;
  MPI_Status status;
  char msg[20];
  MPI_Init(&argc,&argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &myid);
  MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
  if(myid == 0){
    strcpy(msg, "Hello,World");
    MPI_Send(msg, 20, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
  }
  else if (myid == 1){
    MPI_Recv(msg, 20, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);
    printf("%s\n", msg);
    fflush(stdout);
  }
  MPI_Finalize();
  return 0;
}
[radio@taka sub1]$ mpicc mpi_test1.c -o mpi_test1c
[radio@taka sub1]$ mpirun -np 3 mpi_test1c
Hello,World

後で見るようにこのsendという関数はかなり汎用性が高く、点対点の送信関数としては最も一般的なものになる。

また、receiveは任意の型を返すが、どこから送られてきたのか、何番のタグのデータだったのかは直接返すことはない(もちろんreceiveの中でタグと相手のランクを指定しているときは、簡単に調べられるのだが)。これらのデータが手軽に利用したいときにはreceive_statusという関数がある。例を示す。

(* mpi_test2.ml *)
open Mpi
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send "Hello,World" 1 0 comm_world;
 if myrank=1 then
  begin
   let a,b,c = receive_status 0 0 comm_world in
    Printf.printf "rank:%d,tag:%d,%s\n" b c a
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test2.ml -o mpi_test2
[radio@taka sub1]$ mpirun -np 3 mpi_test2
rank:0,tag:0,Hello,World

上のようにデータだけでなく、それがどこのノードの何番のタグのデータなのかを調べられる。これはany_tag,any_sourceという特別なタグとランクを使っているときに特に有用だ。any_tagとany_sourceはC言語のMPIにあるようにあらゆるタグとソースにマッチする。以下に例を示す。

(* mpi_test3.ml *)
open Mpi
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then
  begin
   send "Data1" 2 0 comm_world;
   send "Data2" 2 1 comm_world;
  end;
 if myrank=1 then
  begin
   send "Data3" 2 2 comm_world;
   send "Data4" 2 3 comm_world;
  end;
  if myrank=2 then
   begin
    print_endline (receive any_source any_tag comm_world);
    print_endline (receive any_source any_tag comm_world);
    let a,b,c = receive_status any_source any_tag comm_world and
        d,e,f = receive_status any_source any_tag comm_world in
     Printf.printf "rank:%d, tag:%d, %s\n" b c a;
     Printf.printf "rank:%d, tag:%d, %s\n" e f d;
   end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test3.ml -o mpi_test3
[radio@taka sub1]$ mpirun -np 3 mpi_test3
Data1
Data2
rank:1, tag:2, Data3
rank:1, tag:3, Data4

このようにany_source,any_tagを使った場合でも、相手ランクとタグが判別できる。sendは最も一般的な送信関数であらゆるデータを送ることが出来るが、ocamlmpiにはint,float,int_array,float_array用の特別な送受信関数が用意されている。これらはsend,reciveを使うより高速である。とる型は次の通り。

val send_int : int -> rank -> tag -> communicator -> unit
val receive_int : rank -> tag -> communicator -> int
val send_float : float -> rank -> tag -> communicator -> unit
val receive_float : rank -> tag -> communicator -> float
val send_int_array : int array -> rank -> tag -> communicator -> unit
val receive_int_array : int array -> rank -> tag -> communicator -> unit
val send_float_array : float array -> rank -> tag -> communicator -> unit
val receive_falot_array : float_array -> rank -> tag -> communicator -> unit

注意して欲しいのは配列を受け取るときは受け皿用の配列を用意する必要があることだ。これらの関数を試してみる。

(* mpi_test4.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send_int 1 1 0 comm_world;
 if myrank=1 then printf "int: %d\n" (receive_int 0 0 comm_world);
 if myrank=0 then send_float 1.2 1 1 comm_world;
 if myrank=1 then printf "float: %f\n" (receive_float 0 1 comm_world);
 if myrank=0 then send_int_array [|1;2;3|] 1 2 comm_world;
 if myrank=1 then
  begin
   let a = Array.make 3 0 in
    receive_int_array a 0 2 comm_world;
    Array.iteri (fun x y -> printf "int_array[%d]: %d\n" x y) a;
  end;
 if myrank=0 then send_float_array [|1.2;3.1;4.5|] 1 3 comm_world;
 if myrank=1 then
  begin
   let a = Array.make 3 0.0 in
    receive_float_array a 0 3 comm_world;
    Array.iteri (fun x y -> printf "float_array[%d]: %f\n" x y) a;
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test4.ml -o mpi_test4
[radio@taka sub1]$ mpirun -np 3 mpi_test4
int: 1
float: 1.200000
int_array[0]: 1
int_array[1]: 2
int_array[2]: 3
float_array[0]: 1.200000
float_array[1]: 3.100000
float_array[2]: 4.500000

点対点通信の最後としてsendによって文字列以外のさまざまなものを送受信してみる。もう一度send,receiveの型を確認しておくと

val send : 'a -> rank -> tag -> communicator -> unit
val receive : rank -> tag -> communicator -> 'a

である。'aというのは任意の型を取れるという意味なので、当然send,receiveはsend_int,send_int_arrayなどにも使える。そこで、mpi_test4.mlをsend,receiveを使って書き直してみる。

(* mpi_test5.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send 1 1 0 comm_world;
 if myrank=1 then printf "int: %d\n" (receive 0 0 comm_world);
 if myrank=0 then send 1.2 1 1 comm_world;
 if myrank=1 then printf "float: %f\n" (receive 0 1 comm_world);
 if myrank=0 then send [|1;2;3|] 1 2 comm_world;
 if myrank=1 then
  begin
   let a = receive 0 2 comm_world in
    Array.iteri (fun x y -> printf "int_array[%d]: %d\n" x y) a;
  end;
  if myrank=0 then send [|1.2;3.1;4.5|] 1 3 comm_world;
  if myrank=1 then
   begin
    let a = receive 0 3 comm_world in
     Array.iteri (fun x y -> printf "float_array[%d]: %f\n" x y) a;
   end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test5.ml -o mpi_test5
[radio@taka sub1]$ mpirun -np 3 mpi_test5
int: 1
float: 1.200000
int_array[0]: 1
int_array[1]: 2
int_array[2]: 3
float_array[0]: 1.200000
float_array[1]: 3.100000
float_array[2]: 4.500000

ただ、このままだとreceiveで受け取る型がint arrayなのがソースに明示的に書いていないので少々気持ち悪い。そこで次のようにする。

(* mpi_test6.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send 1 1 0 comm_world;
 if myrank=1 then printf "int: %d\n" (receive 0 0 comm_world);
 if myrank=0 then send 1.2 1 1 comm_world;
 if myrank=1 then printf "float: %f\n" (receive 0 1 comm_world);
 if myrank=0 then send [|1;2;3|] 1 2 comm_world;
 if myrank=1 then
  begin
   let a : int array = receive 0 2 comm_world in
    Array.iteri (fun x y -> printf "int_array[%d]: %d\n" x y) a;
  end;
  if myrank=0 then send [|1.2;3.1;4.5|] 1 3 comm_world;
  if myrank=1 then
   begin
    let a : float array = receive 0 3 comm_world in
     Array.iteri (fun x y -> printf "float_array[%d]: %f\n" x y) a;
   end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test6.ml -o mpi_test6
[radio@taka sub1]$ mpirun -np 3 mpi_test6
int: 1
float: 1.200000
int_array[0]: 1
int_array[1]: 2
int_array[2]: 3
float_array[0]: 1.200000
float_array[1]: 3.100000
float_array[2]: 4.500000

つまり上のようにして、変数を束縛するときに型の制約をかけておく。こうするとソースが読みやすくなる。

リストを送受信してみる。

(* mpi_test7.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send [1;2;3] 1 0 comm_world;
 if myrank=1 then
  begin
   let a : int list = receive 0 0 comm_world in
   List.iter (fun a -> printf "List data: %d\n" a) a;
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test7.ml -o mpi_test7
[radio@taka sub1]$ mpirun -np 3 mpi_test7
List data: 1
List data: 2
List data: 3

タプルを送受信してみる。

(* mpi_test8.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send (1.2,"test",3) 1 0 comm_world;
 if myrank=1 then
  begin
   let a : float * string * int = receive 0 0 comm_world in
   let b,c,d = a in
    printf "tuple data: %f, %s , %d\n" b c d
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test8.ml -o mpi_test8
[radio@taka sub1]$ mpirun -np 3 mpi_test8
tuple data: 1.200000, test , 3

レコードを送受信してみる。

(* mpi_test9.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
type complex = {re:float; im:float}
let _ =
 if myrank=0 then send {re=1.2;im=3.2} 1 0 comm_world;
 if myrank=1 then
  begin
   let a : complex = receive 0 0 comm_world in
    printf "complex: %f, %f\n" a.re a.im
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test9.ml -o mpi_test9
[radio@taka sub1]$ mpirun -np 3 mpi_test9
complex: 1.200000, 3.200000

バリアントを送受信してみる。

(* mpi_test10.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
type var1 = Positive | Negative
type var2 = Data1 of int * int * int | Data2 of float * float * float
let _ =
 if myrank=0 then send Negative 1 0 comm_world;
 if myrank=1 then
  if Positive = (receive 0 0 comm_world)
  then printf "data is positive\n"
  else printf "data is negative\n";
 if myrank=0 then send (Data1(1,2,3)) 1 1 comm_world;
 if myrank=1 then
  let a : var2 = receive 0 1 comm_world in
   match a with
   | Data1(x,y,z) -> printf "Data1:%d,%d,%d\n" x y z
   | Data2(x,y,z) -> printf "Data2:%f,%f,%f\n" x y z
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test10.ml -o mpi_test10
[radio@taka sub1]$ mpirun -np 3 mpi_test10
data is negative
Data1:1,2,3

インスタンスを送受信してみる。

(* mpi_test11.ml *)
open Mpi
open Printf
class point(x_init,y_init) =
object
  val mutable x = x_init;
  val mutable y = y_init;
  method get_x = x;
  method get_y = y;
  method incr_x = x <- x+1;
  method incr_y = y <- y+1;
end
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send (new point (2,4)) 1 0 comm_world;
 if myrank=1 then
  begin
   let a : point = receive 0 0 comm_world in
    printf "object x:%d,y:%d\n" a#get_x a#get_y;
    a#incr_x; a#incr_x; a#incr_y;
    printf "object x:%d,y:%d\n" a#get_x a#get_y;
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test11.ml -o mpi_test11
[radio@taka sub1]$ mpirun -np 3 mpi_test11
object x:2,y:4
object x:4,y:5

インスタンスのリストを送受信してみる。

(* mpi_test12.ml *)
open Mpi
open Printf
class point(x_init,y_init) =
object
  val mutable x = x_init;
  val mutable y = y_init;
  method get_x = x;
  method get_y = y;
  method incr_x = x <- x+1;
  method incr_y = y <- y+1;
end
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then
  begin
   let a = new point(2,4) and b = new point(3,1) and c = new point(5,7) in
    send [a;b;c] 1 0 comm_world;
  end;
 if myrank=1 then
  begin
   let a : point list = receive 0 0 comm_world in
    List.iter (fun o -> printf "List data: %d %d\n" o#get_x o#get_y) a;
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test12.ml -o mpi_test12
[radio@taka sub1]$ mpirun -np 3 mpi_test12
List data: 2 4
List data: 3 1
List data: 5 7

関数を送受信してみる。

(* mpi_test13.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 if myrank=0 then send (fun a b -> a + b) 1 0 comm_world;
 if myrank=1 then
  begin
   let f : int->int->int = receive 0 0 comm_world in
    printf "function result: %d\n" (f 2 3)
  end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test13.ml -o mpi_test13
[radio@taka sub1]$ mpirun -np 3 mpi_test13
function result: 5

Broadcast

Broadcastは集団通信の最も基本的なものだ。すべてのノードにルートノードからのデータを送る。ocamlmpiで定義されているbroadcast関連の関数は次のようなものがある。

val broadcast : 'a -> rank -> communicator -> 'a
val broadcast_int : int -> rank -> communicator -> int
val broadcast_float : float -> rank -> communicator -> float
val broadcast_int_array : int array -> rank -> communicator -> unit
val broadcast_float_array : float array -> rank -> communicator -> unit

これらの関数で重要なのはbroadcast,broadcast_int,broadcast_floatが送信されたデータが関数の返り値になるため、最初の送信するデータを表す引数はルートノードでのみ重要だが、broadcast_int_array,broadcast_float_arrayはunit型の返り値をもつ。つまり、結果は最初の引数に格納されるので、全てのノードでこの引数は意味を持つ。次にこれらの関数の実際の使い方を見ていく。

まずはbroadcastの使い方。

(* mpi_test14.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = broadcast "Hello,world" 0 comm_world in
  printf "comm_size: %d , myrank: %d, %s\n" size myrank a
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test14.ml -o mpi_test14
[radio@taka sub1]$ mpirun -np 3 mpi_test14
comm_size: 3 , myrank: 2, Hello,world
comm_size: 3 , myrank: 1, Hello,world
comm_size: 3 , myrank: 0, Hello,world

send,receiveなどと違ってrankによってif文で分岐させる必要がないことが分かる。

次はbroadcastの特殊な場合の関数群。

(* mpi_test15.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 printf "comm_size: %d , myrank: %d, int data:%d\n" size myrank
        (broadcast_int 3 0 comm_world);
 printf "comm_size: %d , myrank: %d, float data:%f\n" size myrank
        (broadcast_float 2.9 0 comm_world);
 let a = broadcast [|2;3;4|] 0 comm_world and
     b = broadcast [|2.4;3.5|] 0 comm_world in
Array.iteri (fun m n -> printf "rank:%d int array[%d]: %d\n" myrank m n) a;
Array.iteri (fun m n -> printf "rank:%d float_array[%d]: %f\n" myrank m n) \
    b
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test15.ml -o mpi_test15
[radio@taka sub1]$ mpirun -np 3 mpi_test15
comm_size: 3 , myrank: 2, int data:3
comm_size: 3 , myrank: 2, float data:2.900000
rank:2 int array[0]: 2
rank:2 int array[1]: 3
rank:2 int array[2]: 4
rank:2 float_array[0]: 2.400000
rank:2 float_array[1]: 3.500000
comm_size: 3 , myrank: 1, int data:3
comm_size: 3 , myrank: 1, float data:2.900000
rank:1 int array[0]: 2
rank:1 int array[1]: 3
rank:1 int array[2]: 4
rank:1 float_array[0]: 2.400000
rank:1 float_array[1]: 3.500000
comm_size: 3 , myrank: 0, int data:3
comm_size: 3 , myrank: 0, float data:2.900000
rank:0 int array[0]: 2
rank:0 int array[1]: 3
rank:0 int array[2]: 4
rank:0 float_array[0]: 2.400000
rank:0 float_array[1]: 3.500000

全てのランクについてデータが渡されているのが分かる。

Scatter

scatter関連の関数の型は次の通り。

val scatter : 'a array -> rank -> communicator -> 'a
val scatter_int : int array -> rank -> communicator -> int
val scatter_float : float array -> rank -> communicator -> float
val scatter_int_array : int array -> int array -> rank -> communicator -> unit
val scatter_float_array : float array -> float_array -> rank -> communicator -> unit

scatterは配列を引数にとって、それを各プロセスに等分割して送る。例えば、scatterの場合はルートノードが配列aを送るとすると、ランクが1のノードはa[1]のデータを受け取る。まずはscatterの使い方を見る。

(* mpi_test16.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = scatter [|1;2;3|] 0 comm_world in
  printf "comm_size: %d , myrank: %d , data: %d\n" size myrank a
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test16.ml -o mpi_test16
[radio@taka sub1]$ mpirun -np 3 mpi_test16
comm_size: 3 , myrank: 2 , data: 3
comm_size: 3 , myrank: 1 , data: 2
comm_size: 3 , myrank: 0 , data: 1

このようにデータを分散して、送ることが出来る。ただ、例えばここで送る配列のサイズが4でプロセス数が3のままだとこのプログラムでは正常に動作しない。つまり送る配列のサイズはcommunicatorのサイズと同一のサイズである必要があるようだ。

次に他のscatter関数を見ていく。基本的にsend,receiveと同じような形式をとる。

(* mpi_test17.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = scatter_int [|1;2;3|] 0 comm_world
 and b = scatter_float [|2.3;3.1;4.3|] 0 comm_world
 and c = Array.make 2 0
 and d = Array.make 2 0.0 in
   printf "myrank:%d, data=%d\n" myrank a;
   printf "myrank:%d, data=%f\n" myrank b;
   scatter_int_array [|5;6;7;8;9;10|] c 0 comm_world;
   scatter_float_array [|3.1;4.6;9.1;4.5;5.1;8.9|] d 0 comm_world;
   Array.iteri (fun x y -> printf "myrank:%d, data[%d]=%d\n" myrank x y) c;
   Array.iteri (fun x y -> printf "myrank:%d, data[%d]=%f\n" myrank x y) d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test17.ml -o mpi_test17
[radio@taka sub1]$ mpirun -np 3 mpi_test17
myrank:2, data=3
myrank:2, data=4.300000
myrank:2, data[0]=9
myrank:2, data[1]=10
myrank:2, data[0]=5.100000
myrank:2, data[1]=8.900000
myrank:1, data=2
myrank:1, data=3.100000
myrank:1, data[0]=7
myrank:1, data[1]=8
myrank:1, data[0]=9.100000
myrank:1, data[1]=4.500000
myrank:0, data=1
myrank:0, data=2.300000
myrank:0, data[0]=5
myrank:0, data[1]=6
myrank:0, data[0]=3.100000
myrank:0, data[1]=4.600000

このように送ることが出来る。ただ、これも配列の要素数を間違えると実行時に無限ループのような感じになって、正常に動作しない。

Gather

gatherは各ノードからデータを集めてくることが出来る関数。ocamlmpiには次の関数が定義されている。

val gather : 'a -> rank -> communicator -> 'a array
val gather_int : int -> int array -> rank -> communicator -> unit
val gather_float : float -> float array -> rank -> communicator -> unit
val gahter_int_array : int array -> int array -> rank -> communicator -> unit
val gather_float_array : float_array -> float_array -> rank -> communicator -> unit

例えば、gatherは各ノードから送られるデータを集めて配列にしたものがルートノードに返り値として渡され、他のノードには空のノードが渡される。もしその内容を他のノードでも利用したいのなら、後述のallgather関数を利用する。

gatherの例を挙げる。

(* mpi_test18.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = gather myrank 0 comm_world in
  if myrank=0 then
   Array.iteri (fun x y -> printf "rank:%d,a[%d],%d\n" myrank x y) a
  else
   printf "rank:%d , array length %d\n" myrank (Array.length a)
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test18.ml -o mpi_test18
[radio@taka sub1]$ mpirun -np 3 mpi_test18
rank:2 , array length 0
rank:1 , array length 0
rank:0,a[0],0
rank:0,a[1],1
rank:0,a[2],2

次に他のgather関数の例を挙げる。

(* mpi_test19.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 3 0 and b = Array.make 3 0.0 and c = Array.make 6 0 and
     d = Array.make 6 0.0 in
  gather_int myrank a 0 comm_world;
  gather_float (float myrank) b 0 comm_world;
  gather_int_array [|myrank;(myrank+1)|] c 0 comm_world;
gather_float_array [|(float myrank);((float myrank)+.1.0)|] d 0 comm_world;
  if myrank=0 then
   begin
    Array.iteri (fun x y -> printf "rank:%d,a[%d]=%d\n" myrank x y) a;
    Array.iteri (fun x y -> printf "rank:%d,b[%d]=%f\n" myrank x y) b;
    Array.iteri (fun x y -> printf "rank:%d,c[%d]=%d\n" myrank x y) c;
    Array.iteri (fun x y -> printf "rank:%d,d[%d]=%f\n" myrank x y) d;
   end
  else
   begin
    printf "rank:%d , array length %d\n" myrank (Array.length a);
    Array.iteri (fun x y -> printf "rank:%d,a[%d]=%d\n" myrank x y) a;
   end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test19.ml -o mpi_test19
[radio@taka sub1]$ mpirun -np 3 mpi_test19
rank:2 , array length 3
rank:2,a[0]=0
rank:2,a[1]=0
rank:2,a[2]=0
rank:1 , array length 3
rank:1,a[0]=0
rank:1,a[1]=0
rank:1,a[2]=0
rank:0,a[0]=0
rank:0,a[1]=1
rank:0,a[2]=2
rank:0,b[0]=0.000000
rank:0,b[1]=1.000000
rank:0,b[2]=2.000000
rank:0,c[0]=0
rank:0,c[1]=1
rank:0,c[2]=1
rank:0,c[3]=2
rank:0,c[4]=2
rank:0,c[5]=3
rank:0,d[0]=0.000000
rank:0,d[1]=1.000000
rank:0,d[2]=1.000000
rank:0,d[3]=2.000000
rank:0,d[4]=2.000000
rank:0,d[5]=3.000000

ランク1と2は結果が格納されず初期値のままなのが分かる。

Gather to all

gatherした結果を全てのノードで共有したいときはallgather関数を使う。allgather関数の型は次の通り。

val allgather : 'a -> communicator -> 'a array
val allgather_int : int -> int array -> communicator -> unit
val allgather_float : float -> float_array -> communicator -> unit
val allgather_int_array : int array -> int array -> communicator -> unit
val allgather_float_array : float array -> float array -> communicator -> unit

gather関数のサンプルプログラムを一部だけ変更してallgather関数を使ったものにしてみる。

(* mpi_test20.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = allgather myrank comm_world in
  Array.iteri (fun x y -> printf "rank:%d,a[%d],%d\n" myrank x y) a
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test20.ml -o mpi_test20
[radio@taka sub1]$ mpirun -np 3 mpi_test20
rank:2,a[0],0
rank:2,a[1],1
rank:2,a[2],2
rank:1,a[0],0
rank:1,a[1],1
rank:1,a[2],2
rank:0,a[0],0
rank:0,a[1],1
rank:0,a[2],2
(* mpi_test21.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 3 0 and b = Array.make 3 0.0 and c = Array.make 6 0 and
     d = Array.make 6 0.0 in
  allgather_int myrank a comm_world;
  allgather_float (float myrank) b comm_world;
  allgather_int_array [|myrank;(myrank+1)|] c comm_world;
allgather_float_array [|(float myrank);((float myrank)+.1.0)|] d \
    comm_world;
  Array.iteri (fun x y -> printf "rank:%d,a[%d]=%d\n" myrank x y) a;
  Array.iteri (fun x y -> printf "rank:%d,b[%d]=%f\n" myrank x y) b;
  Array.iteri (fun x y -> printf "rank:%d,c[%d]=%d\n" myrank x y) c;
  Array.iteri (fun x y -> printf "rank:%d,c[%d]=%f\n" myrank x y) d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test21.ml -o mpi_test21
[radio@taka sub1]$ mpirun -np 3 mpi_test21
rank:2,a[0]=0
rank:2,a[1]=1
rank:2,a[2]=2
rank:2,b[0]=0.000000
rank:2,b[1]=1.000000
rank:2,b[2]=2.000000
rank:2,c[0]=0
rank:2,c[1]=1
rank:2,c[2]=1
rank:2,c[3]=2
rank:2,c[4]=2
rank:2,c[5]=3
rank:2,c[0]=0.000000
rank:2,c[1]=1.000000
rank:2,c[2]=1.000000
rank:2,c[3]=2.000000
rank:2,c[4]=2.000000
rank:2,c[5]=3.000000
rank:1,a[0]=0
rank:1,a[1]=1
rank:1,a[2]=2
rank:1,b[0]=0.000000
rank:1,b[1]=1.000000
rank:1,b[2]=2.000000
rank:1,c[0]=0
rank:1,c[1]=1
rank:1,c[2]=1
rank:1,c[3]=2
rank:1,c[4]=2
rank:1,c[5]=3
rank:1,c[0]=0.000000
rank:1,c[1]=1.000000
rank:1,c[2]=1.000000
rank:1,c[3]=2.000000
rank:1,c[4]=2.000000
rank:1,c[5]=3.000000
rank:0,a[0]=0
rank:0,a[1]=1
rank:0,a[2]=2
rank:0,b[0]=0.000000
rank:0,b[1]=1.000000
rank:0,b[2]=2.000000
rank:0,c[0]=0
rank:0,c[1]=1
rank:0,c[2]=1
rank:0,c[3]=2
rank:0,c[4]=2
rank:0,c[5]=3
rank:0,c[0]=0.000000
rank:0,c[1]=1.000000
rank:0,c[2]=1.000000
rank:0,c[3]=2.000000
rank:0,c[4]=2.000000
rank:0,c[5]=3.000000

長いが、全てのノードにデータが渡されているのが分かる。

Reduce

reduce,allreduce,scanは各ノードのint型、もしくはfloat型のデータに対して演算を行う関数だ。演算の内容は次のように定義されている。

type intop = Int_max | Int_min | Int_sum | Int_prod | Int_land | Int_lor | Int_xor
type floatop = Float_max | Float_min | Float_sum | Float_max

名前から想像付くように、max,minは最大値、sum,prodは和、積、land,lor,xorは論理演算のand,or,xorである。reduce,allreduce,scanはこれらの演算子を使い演算を行う。

まずはreduceから説明する。reduceには次のような関数が定義されている。

val reduce_int : int -> intop -> rank -> communicator -> int
val reduce_float : float -> floatop -> rank -> communicator -> float
val reduce_int_array : int array -> int array -> intop -> rank -> communicator -> unit
val reuduce_float_array : float array -> float array -> floatop -> rank -> communicator -> unit

ここで注意して欲しいのはreduce_int_arrayとreduce_float_arrayは配列のデータ全てに対してintop,floatopを実行した一つのint型、float型の結果を返すわけではないということだ。つまり、reduce_int_arrayとreduce_float_arrayは各ノードの第一引数の配列の添え字が同じもの同士に対してintop,floatopを実行する。各関数に対して例を挙げる。

まずはreduce_int。

(* mpi_test22.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = reduce_int myrank Int_max 0 comm_world and
     b = reduce_int myrank Int_min 0 comm_world and
     c = reduce_int myrank Int_sum 0 comm_world and
     d = reduce_int myrank Int_prod 0 comm_world and
     e = reduce_int myrank Int_land 0 comm_world and
     f = reduce_int myrank Int_lor 0 comm_world and
     g = reduce_int myrank Int_xor 0 comm_world in
  if myrank=0 then
   begin
    printf "Int_max: %d\n" a;
    printf "Int_min: %d\n" b;
    printf "Int_sum: %d\n" c;
    printf "Int_prod: %d\n" d;
    printf "Int_land: %d\n" e;
    printf "Int_lor: %d\n" f;
    printf "Int_xor: %d\n" g;
   end;
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test22.ml -o mpi_test22
[radio@taka sub1]$ mpirun -np 3 mpi_test22
Int_max: 2
Int_min: 0
Int_sum: 3
Int_prod: 0
Int_land: 0
Int_lor: 3
Int_xor: 3

次はreduce_float。

(* mpi_test23.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = reduce_float (float myrank) Float_max 0 comm_world and
     b = reduce_float (float myrank) Float_min 0 comm_world and
     c = reduce_float (float myrank) Float_sum 0 comm_world and
     d = reduce_float (float myrank) Float_prod 0 comm_world in
  if myrank=0 then
   begin
    printf "Float_max: %f\n" a;
    printf "Float_min: %f\n" b;
    printf "Float_sum: %f\n" c;
    printf "Float_prod: %f\n" d;
   end
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test23.ml -o mpi_test23
[radio@taka sub1]$ mpirun -np 3 mpi_test23
Float_max: 2.000000
Float_min: 0.000000
Float_sum: 3.000000
Float_prod: 0.000000

reduce_int_array。

(* mpi_test24.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 2 0 and b = Array.make 2 0 and c = Array.make 2 0
 and d = Array.make 2 0 and e = Array.make 2 0 and f = Array.make 2 0
 and g = Array.make 2 0 in
  reduce_int_array [|myrank;myrank+1|] a Int_max 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] b Int_min 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] c Int_sum 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] d Int_prod 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] e Int_land 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] f Int_lor 0 comm_world;
  reduce_int_array [|myrank;myrank+1|] g Int_xor 0 comm_world;
  if myrank=0 then
   begin
    Array.iteri (fun x y -> printf "Int_max:a[%d]=%d\n" x y) a;
    Array.iteri (fun x y -> printf "Int_min:b[%d]=%d\n" x y) b;
    Array.iteri (fun x y -> printf "Int_sum:c[%d]=%d\n" x y) c;
    Array.iteri (fun x y -> printf "Int_prod:d[%d]=%d\n" x y) d;
    Array.iteri (fun x y -> printf "Int_land:e[%d]=%d\n" x y) e;
    Array.iteri (fun x y -> printf "Int_lor:f[%d]=%d\n" x y) f;
    Array.iteri (fun x y -> printf "Int_xor:g[%d]=%d\n" x y) g;
   end;
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test24.ml -o mpi_test24
[radio@taka sub1]$ mpirun -np 3 mpi_test24
Int_max:a[0]=2
Int_max:a[1]=3
Int_min:b[0]=0
Int_min:b[1]=1
Int_sum:c[0]=3
Int_sum:c[1]=6
Int_prod:d[0]=0
Int_prod:d[1]=6
Int_land:e[0]=0
Int_land:e[1]=0
Int_lor:f[0]=3
Int_lor:f[1]=3
Int_xor:g[0]=3
Int_xor:g[1]=0

reduce_float_array。

(* mpi_test25.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 2 0.0 and b = Array.make 2 0.0 and c = Array.make 2 0.0
 and d = Array.make 2 0.0 in
reduce_float_array [|(float myrank);((float myrank)+.1.0)|] a Float_max 0 \
    comm_world;
reduce_float_array [|(float myrank);((float myrank)+.1.0)|] b Float_min 0 \
    comm_world;
reduce_float_array [|(float myrank);((float myrank)+.1.0)|] c Float_sum 0 \
    comm_world;
reduce_float_array [|(float myrank);((float myrank)+.1.0)|] d Float_prod 0 \
    comm_world;
  if myrank=0 then
   begin
    Array.iteri (fun x y -> printf "Float_max:a[%d]=%f\n" x y) a;
    Array.iteri (fun x y -> printf "Float_min:b[%d]=%f\n" x y) b;
    Array.iteri (fun x y -> printf "Float_sum:c[%d]=%f\n" x y) c;
    Array.iteri (fun x y -> printf "Float_prod:d[%d]=%f\n" x y) d;
   end;
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test25.ml -o mpi_test25
[radio@taka sub1]$ mpirun -np 3 mpi_test25
Float_max:a[0]=2.000000
Float_max:a[1]=3.000000
Float_min:b[0]=0.000000
Float_min:b[1]=1.000000
Float_sum:c[0]=3.000000
Float_sum:c[1]=6.000000
Float_prod:d[0]=0.000000
Float_prod:d[1]=6.000000

Reduce to all

allreduceはreduceとほとんど同じだが、結果が全てのノードで共有されるところがreduceと異なっている。次のような型をとる。

val allreduce_int : int -> intop -> communicator -> int
val allreduce_float : float -> floatop -> communicator -> float
val allreduce_int_array : int array -> int array -> intop -> communicator -> unit
val allreduce_float_array : float array -> float array -> floatop -> communicator -> unit

reduceの関数のサンプルプログラムを一部変更してallreduceを使ったものに書き換えてみる。

まずはallreduce_int。

(* mpi_test26.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
let a = allreduce_int myrank Int_max comm_world and b = allreduce_int \
    myrank Int_min comm_world
and c = allreduce_int myrank Int_sum comm_world and d = allreduce_int \
    myrank Int_prod comm_world
and e = allreduce_int myrank Int_land comm_world and f = allreduce_int \
    myrank Int_lor comm_world
 and g = allreduce_int myrank Int_xor comm_world in
  printf "rank :[%d] Int_max: %d\n" myrank a;
  printf "rank :[%d] Int_min: %d\n" myrank b;
  printf "rank :[%d] Int_sum: %d\n" myrank c;
  printf "rank :[%d] Int_prod: %d\n" myrank d;
  printf "rank :[%d] Int_land: %d\n" myrank e;
  printf "rank :[%d] Int_lor: %d\n" myrank f;
  printf "rank :[%d] Int_xor: %d\n" myrank g
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test26.ml -o mpi_test26
[radio@taka sub1]$ mpirun -np 3 mpi_test26
rank :[2] Int_max: 2
rank :[2] Int_min: 0
rank :[2] Int_sum: 3
rank :[2] Int_prod: 0
rank :[2] Int_land: 0
rank :[2] Int_lor: 3
rank :[2] Int_xor: 3
rank :[1] Int_max: 2
rank :[1] Int_min: 0
rank :[1] Int_sum: 3
rank :[1] Int_prod: 0
rank :[1] Int_land: 0
rank :[1] Int_lor: 3
rank :[1] Int_xor: 3
rank :[0] Int_max: 2
rank :[0] Int_min: 0
rank :[0] Int_sum: 3
rank :[0] Int_prod: 0
rank :[0] Int_land: 0
rank :[0] Int_lor: 3
rank :[0] Int_xor: 3

上のように結果が全てのノードに渡されているのが分かる。

次にallreduce_float。

(* mpi_test27.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = allreduce_float (float myrank) Float_max comm_world and
     b = allreduce_float (float myrank) Float_min comm_world and
     c = allreduce_float (float myrank) Float_sum comm_world and
     d = allreduce_float (float myrank) Float_prod comm_world in
  printf "rank:[%d] Float_max: %f\n" myrank a;
  printf "rank:[%d] Float_min: %f\n" myrank b;
  printf "rank:[%d] Float_sum: %f\n" myrank c;
  printf "rank:[%d] Float_prod: %f\n" myrank d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test27.ml -o mpi_test27
[radio@taka sub1]$ mpirun -np 3 mpi_test27
rank:[2] Float_max: 2.000000
rank:[2] Float_min: 0.000000
rank:[2] Float_sum: 3.000000
rank:[2] Float_prod: 0.000000
rank:[1] Float_max: 2.000000
rank:[1] Float_min: 0.000000
rank:[1] Float_sum: 3.000000
rank:[1] Float_prod: 0.000000
rank:[0] Float_max: 2.000000
rank:[0] Float_min: 0.000000
rank:[0] Float_sum: 3.000000
rank:[0] Float_prod: 0.000000

allreduce_int_array。

(* mpi_test28.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 2 0 and b = Array.make 2 0 and c = Array.make 2 0
 and d = Array.make 2 0 and e = Array.make 2 0 and f = Array.make 2 0
 and g = Array.make 2 0 in
  allreduce_int_array [|myrank;myrank+1|] a Int_max comm_world;
  allreduce_int_array [|myrank;myrank+1|] b Int_min comm_world;
  allreduce_int_array [|myrank;myrank+1|] c Int_sum comm_world;
  allreduce_int_array [|myrank;myrank+1|] d Int_prod comm_world;
  allreduce_int_array [|myrank;myrank+1|] e Int_land comm_world;
  allreduce_int_array [|myrank;myrank+1|] f Int_lor comm_world;
  allreduce_int_array [|myrank;myrank+1|] g Int_xor comm_world;
Array.iteri (fun x y -> printf "rank[%d] Int_max:a[%d]=%d\n" myrank x y) a;
Array.iteri (fun x y -> printf "rank[%d] Int_min:b[%d]=%d\n" myrank x y) b;
Array.iteri (fun x y -> printf "rank[%d] Int_sum:c[%d]=%d\n" myrank x y) c;
Array.iteri (fun x y -> printf "rank[%d] Int_prod:d[%d]=%d\n" myrank x y) \
    d;
Array.iteri (fun x y -> printf "rank[%d] Int_land:e[%d]=%d\n" myrank x y) \
    e;
Array.iteri (fun x y -> printf "rank[%d] Int_lor:f[%d]=%d\n" myrank x y) f;
Array.iteri (fun x y -> printf "rank[%d] Int_xor:g[%d]=%d\n" myrank x y) g
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test28.ml -o mpi_test28
[radio@taka sub1]$ mpirun -np 3 mpi_test28
rank[2] Int_max:a[0]=2
rank[2] Int_max:a[1]=3
rank[2] Int_min:b[0]=0
rank[2] Int_min:b[1]=1
rank[2] Int_sum:c[0]=3
rank[2] Int_sum:c[1]=6
rank[2] Int_prod:d[0]=0
rank[2] Int_prod:d[1]=6
rank[2] Int_land:e[0]=0
rank[2] Int_land:e[1]=0
rank[2] Int_lor:f[0]=3
rank[2] Int_lor:f[1]=3
rank[2] Int_xor:g[0]=3
rank[2] Int_xor:g[1]=0
rank[1] Int_max:a[0]=2
rank[1] Int_max:a[1]=3
rank[1] Int_min:b[0]=0
rank[1] Int_min:b[1]=1
rank[1] Int_sum:c[0]=3
rank[1] Int_sum:c[1]=6
rank[1] Int_prod:d[0]=0
rank[1] Int_prod:d[1]=6
rank[1] Int_land:e[0]=0
rank[1] Int_land:e[1]=0
rank[1] Int_lor:f[0]=3
rank[1] Int_lor:f[1]=3
rank[1] Int_xor:g[0]=3
rank[1] Int_xor:g[1]=0
rank[0] Int_max:a[0]=2
rank[0] Int_max:a[1]=3
rank[0] Int_min:b[0]=0
rank[0] Int_min:b[1]=1
rank[0] Int_sum:c[0]=3
rank[0] Int_sum:c[1]=6
rank[0] Int_prod:d[0]=0
rank[0] Int_prod:d[1]=6
rank[0] Int_land:e[0]=0
rank[0] Int_land:e[1]=0
rank[0] Int_lor:f[0]=3
rank[0] Int_lor:f[1]=3
rank[0] Int_xor:g[0]=3
rank[0] Int_xor:g[1]=0

allreduce_float_array。

(* mpi_test29.ml *)
open Mpi
open Printf 

let size = comm_size comm_world
let myrank = comm_rank comm_world

let _ =
 let a = Array.make 2 0.0 and b = Array.make 2 0.0 and c = Array.make 2 0.0
 and d = Array.make 2 0.0 in
allreduce_float_array [|(float myrank);((float myrank)+.1.0)|] a Float_max \
    comm_world;
allreduce_float_array [|(float myrank);((float myrank)+.1.0)|] b Float_min \
    comm_world;
allreduce_float_array [|(float myrank);((float myrank)+.1.0)|] c Float_sum \
    comm_world;
allreduce_float_array [|(float myrank);((float myrank)+.1.0)|] d Float_prod \
    comm_world;
Array.iteri (fun x y -> printf "rank[%d] Float_max:a[%d]=%f\n" myrank x y) \
    a;
Array.iteri (fun x y -> printf "rank[%d] Float_min:b[%d]=%f\n" myrank x y) \
    b;
Array.iteri (fun x y -> printf "rank[%d] Float_sum:c[%d]=%f\n" myrank x y) \
    c;
Array.iteri (fun x y -> printf "rank[%d] Float_prod:d[%d]=%f\n" myrank x y) \
    d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test29.ml -o mpi_test29
[radio@taka sub1]$ mpirun -np 3 mpi_test29
rank[2] Float_max:a[0]=2.000000
rank[2] Float_max:a[1]=3.000000
rank[2] Float_min:b[0]=0.000000
rank[2] Float_min:b[1]=1.000000
rank[2] Float_sum:c[0]=3.000000
rank[2] Float_sum:c[1]=6.000000
rank[2] Float_prod:d[0]=0.000000
rank[2] Float_prod:d[1]=6.000000
rank[1] Float_max:a[0]=2.000000
rank[1] Float_max:a[1]=3.000000
rank[1] Float_min:b[0]=0.000000
rank[1] Float_min:b[1]=1.000000
rank[1] Float_sum:c[0]=3.000000
rank[1] Float_sum:c[1]=6.000000
rank[1] Float_prod:d[0]=0.000000
rank[1] Float_prod:d[1]=6.000000
rank[0] Float_max:a[0]=2.000000
rank[0] Float_max:a[1]=3.000000
rank[0] Float_min:b[0]=0.000000
rank[0] Float_min:b[1]=1.000000
rank[0] Float_sum:c[0]=3.000000
rank[0] Float_sum:c[1]=6.000000
rank[0] Float_prod:d[0]=0.000000
rank[0] Float_prod:d[1]=6.000000

Scan

scan関数はreduceと似ているが、各ノードのランク数によって演算対象となるデータが異なる。つまりランクiのノードはランク0からiまでのデータに対して指示された演算を行った結果を得る。関数の型は次の通り。

val scan_int : int -> intop -> communicator -> int
val scan_falot : float -> floatop -> communicator -> float
val scan_int_array : int array -> int array -> intop -> communicator -> unit
val scan_float_array : float array -> float array -> floatop -> communicator -> unit

次に関数の実際の使い方を見る。

まずはscan_int。

(* mpi_test30.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
let a = scan_int myrank Int_max comm_world and b = scan_int myrank Int_min \
    comm_world
and c = scan_int myrank Int_sum comm_world and d = scan_int myrank Int_prod \
    comm_world
and e = scan_int myrank Int_land comm_world and f = scan_int myrank Int_lor \
    comm_world
 and g = scan_int myrank Int_xor comm_world in
  printf "rank :[%d] Int_max: %d\n" myrank a;
  printf "rank :[%d] Int_min: %d\n" myrank b;
  printf "rank :[%d] Int_sum: %d\n" myrank c;
  printf "rank :[%d] Int_prod: %d\n" myrank d;
  printf "rank :[%d] Int_land: %d\n" myrank e;
  printf "rank :[%d] Int_lor: %d\n" myrank f;
  printf "rank :[%d] Int_xor: %d\n" myrank g
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test30.ml -o mpi_test30
[radio@taka sub1]$ mpirun -np 3 mpi_test30
rank :[1] Int_max: 1
rank :[1] Int_min: 0
rank :[1] Int_sum: 1
rank :[1] Int_prod: 0
rank :[1] Int_land: 0
rank :[1] Int_lor: 1
rank :[1] Int_xor: 1
rank :[2] Int_max: 2
rank :[2] Int_min: 0
rank :[2] Int_sum: 3
rank :[2] Int_prod: 0
rank :[2] Int_land: 0
rank :[2] Int_lor: 3
rank :[2] Int_xor: 3
rank :[0] Int_max: 0
rank :[0] Int_min: 0
rank :[0] Int_sum: 0
rank :[0] Int_prod: 0
rank :[0] Int_land: 0
rank :[0] Int_lor: 0
rank :[0] Int_xor: 0

次はscan_float。

(* mpi_test31.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = scan_float (float myrank) Float_max comm_world and
     b = scan_float (float myrank) Float_min comm_world and
     c = scan_float (float myrank) Float_sum comm_world and
     d = scan_float (float myrank) Float_prod comm_world in
  printf "rank:[%d] Float_max: %f\n" myrank a;
  printf "rank:[%d] Float_min: %f\n" myrank b;
  printf "rank:[%d] Float_sum: %f\n" myrank c;
  printf "rank:[%d] Float_prod: %f\n" myrank d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test31.ml -o mpi_test31
[radio@taka sub1]$ mpirun -np 3 mpi_test31
rank:[2] Float_max: 2.000000
rank:[2] Float_min: 0.000000
rank:[2] Float_sum: 3.000000
rank:[2] Float_prod: 0.000000
rank:[1] Float_max: 1.000000
rank:[1] Float_min: 0.000000
rank:[1] Float_sum: 1.000000
rank:[1] Float_prod: 0.000000
rank:[0] Float_max: 0.000000
rank:[0] Float_min: 0.000000
rank:[0] Float_sum: 0.000000
rank:[0] Float_prod: 0.000000

scan_int_array。

(* mpi_test32.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 2 0 and b = Array.make 2 0 and c = Array.make 2 0
 and d = Array.make 2 0 and e = Array.make 2 0 and f = Array.make 2 0
 and g = Array.make 2 0 in
  scan_int_array [|myrank;myrank+1|] a Int_max comm_world;
  scan_int_array [|myrank;myrank+1|] b Int_min comm_world;
  scan_int_array [|myrank;myrank+1|] c Int_sum comm_world;
  scan_int_array [|myrank;myrank+1|] d Int_prod comm_world;
  scan_int_array [|myrank;myrank+1|] e Int_land comm_world;
  scan_int_array [|myrank;myrank+1|] f Int_lor comm_world;
  scan_int_array [|myrank;myrank+1|] g Int_xor comm_world;
Array.iteri (fun x y -> printf "rank[%d] Int_max:a[%d]=%d\n" myrank x y) a;
Array.iteri (fun x y -> printf "rank[%d] Int_min:b[%d]=%d\n" myrank x y) b;
Array.iteri (fun x y -> printf "rank[%d] Int_sum:c[%d]=%d\n" myrank x y) c;
Array.iteri (fun x y -> printf "rank[%d] Int_prod:d[%d]=%d\n" myrank x y) \
    d;
Array.iteri (fun x y -> printf "rank[%d] Int_land:e[%d]=%d\n" myrank x y) \
    e;
Array.iteri (fun x y -> printf "rank[%d] Int_lor:f[%d]=%d\n" myrank x y) f;
Array.iteri (fun x y -> printf "rank[%d] Int_xor:g[%d]=%d\n" myrank x y) g
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test32.ml -o mpi_test32
[radio@taka sub1]$ mpirun -np 3 mpi_test32
rank[2] Int_max:a[0]=2
rank[2] Int_max:a[1]=3
rank[2] Int_min:b[0]=0
rank[2] Int_min:b[1]=1
rank[2] Int_sum:c[0]=3
rank[2] Int_sum:c[1]=6
rank[2] Int_prod:d[0]=0
rank[2] Int_prod:d[1]=6
rank[2] Int_land:e[0]=0
rank[2] Int_land:e[1]=0
rank[2] Int_lor:f[0]=3
rank[2] Int_lor:f[1]=3
rank[2] Int_xor:g[0]=3
rank[2] Int_xor:g[1]=0
rank[1] Int_max:a[0]=1
rank[1] Int_max:a[1]=2
rank[1] Int_min:b[0]=0
rank[1] Int_min:b[1]=1
rank[1] Int_sum:c[0]=1
rank[1] Int_sum:c[1]=3
rank[1] Int_prod:d[0]=0
rank[1] Int_prod:d[1]=2
rank[1] Int_land:e[0]=0
rank[1] Int_land:e[1]=0
rank[1] Int_lor:f[0]=1
rank[1] Int_lor:f[1]=3
rank[1] Int_xor:g[0]=1
rank[1] Int_xor:g[1]=3
rank[0] Int_max:a[0]=0
rank[0] Int_max:a[1]=1
rank[0] Int_min:b[0]=0
rank[0] Int_min:b[1]=1
rank[0] Int_sum:c[0]=0
rank[0] Int_sum:c[1]=1
rank[0] Int_prod:d[0]=0
rank[0] Int_prod:d[1]=1
rank[0] Int_land:e[0]=0
rank[0] Int_land:e[1]=1
rank[0] Int_lor:f[0]=0
rank[0] Int_lor:f[1]=1
rank[0] Int_xor:g[0]=0
rank[0] Int_xor:g[1]=1

scan_float_array。

(* mpi_test33.ml *)
open Mpi
open Printf
let size = comm_size comm_world
let myrank = comm_rank comm_world
let _ =
 let a = Array.make 2 0.0 and b = Array.make 2 0.0 and c = Array.make 2 0.0
 and d = Array.make 2 0.0 in
scan_float_array [|(float myrank);((float myrank)+.1.0)|] a Float_max \
    comm_world;
scan_float_array [|(float myrank);((float myrank)+.1.0)|] b Float_min \
    comm_world;
scan_float_array [|(float myrank);((float myrank)+.1.0)|] c Float_sum \
    comm_world;
scan_float_array [|(float myrank);((float myrank)+.1.0)|] d Float_prod \
    comm_world;
Array.iteri (fun x y -> printf "rank[%d] Float_max:a[%d]=%f\n" myrank x y) \
    a;
Array.iteri (fun x y -> printf "rank[%d] Float_min:b[%d]=%f\n" myrank x y) \
    b;
Array.iteri (fun x y -> printf "rank[%d] Float_sum:c[%d]=%f\n" myrank x y) \
    c;
Array.iteri (fun x y -> printf "rank[%d] Float_prod:d[%d]=%f\n" myrank x y) \
    d
[radio@taka sub1]$ ocamlc -I +ocamlmpi mpi.cma mpi_test33.ml -o mpi_test33
[radio@taka sub1]$ mpirun -np 3 mpi_test33
rank[2] Float_max:a[0]=2.000000
rank[2] Float_max:a[1]=3.000000
rank[2] Float_min:b[0]=0.000000
rank[2] Float_min:b[1]=1.000000
rank[2] Float_sum:c[0]=3.000000
rank[2] Float_sum:c[1]=6.000000
rank[2] Float_prod:d[0]=0.000000
rank[2] Float_prod:d[1]=6.000000
rank[1] Float_max:a[0]=1.000000
rank[1] Float_max:a[1]=2.000000
rank[1] Float_min:b[0]=0.000000
rank[1] Float_min:b[1]=1.000000
rank[1] Float_sum:c[0]=1.000000
rank[1] Float_sum:c[1]=3.000000
rank[1] Float_prod:d[0]=0.000000
rank[1] Float_prod:d[1]=2.000000
rank[0] Float_max:a[0]=0.000000
rank[0] Float_max:a[1]=1.000000
rank[0] Float_min:b[0]=0.000000
rank[0] Float_min:b[1]=1.000000
rank[0] Float_sum:c[0]=0.000000
rank[0] Float_sum:c[1]=1.000000
rank[0] Float_prod:d[0]=0.000000
rank[0] Float_prod:d[1]=1.000000