OCaml入門3 代表的な標準関数の説明 基本的な型変換。 float_of_int 2;; float 2;; int_of_float 2.0;; int_of_char 'a';; char_of_int 100;; string_of_bool true;; bool_of_string "false";; string_of_int 12;; int_of_string "22";; string_of_float 2.3;; float_of_string "2.5";; 名前を見れば分かるだろうからいちいち説明はしないが、型変換の関数。floatはfloat_of_intと 同じ。 OCamlには出力するための方法がいくつかある。大きく分けると標準出力、標準エラー出力、標準入力 に対する関数を利用する。一番基本的な方法だ。次にチャンネルを利用する方法。これは1番目よりも 一般的でファイルに書き込んだりすることもできる。3番目にはほとんど使わないかも知れないが、 Unixモジュールでシステムコールとしてread,writeを呼び出すということもできる。出力、入力という 仕事ではUnixモジュールを使う意味があまりないかも知れないが、使えるといろいろ便利なこともあるので あとでまとめて解説する。 まずは基本的な出力関数から。これらは標準出力に出力する関数群。 print_int 1;; print_float 2.3;; print_char 2;; print_string "aa";; print_endline "aa";; print_newline ();; print_int,print_float,print_char,print_stringはそれぞれの型を表示する組み込み関数。 print_endline はprint_stringと同じだが最後に改行を加える。print_newlineは改行する 関数。 標準エラー出力に出力する関数群。 prerr_char 'a';; prerr_string "a";; prerr_int 1;; prerr_float 1.2;; prerr_endline "aa";; prerr_newline ();; 標準出力に対する関数と同じ。 なおマニュアルによるとprint_endline,print_endline,prerr_endline,prerr_newline は出力するだけでなくflushを行うようだ。明示的にflushをかけることもできる。 次のようにする。 prerr_char 'a';; flush stderr;; 標準出力の方はいちいち出力してくれるので試せないが次のようにする。 print_char 'a';; flush stdout;; flushは次の型をとる。 val flush : out_channel -> unit つまり、stdout,stderrはデフォルトで定義されているout_channelにすぎない。よって channelを操作する関数は基本的にすべてstdout,stderrにも適用可能である。 channelを指定しないflush_allというものもある。 val flush_all : unit -> unit 標準入力関数: 標準出力、標準エラー出力がデフォルトで定義されているout_channelであるように 標準入力はデフォルトで定義されているin_channelに過ぎない。 read_line();; asdsda [Enter] read_int ();; 12 [Enter] read_float ();; 2.3 [Enter] 特に説明は要らないだろうがとる型だけは注目してほしい。read_int,read_floatはint、floatを返すので そのまま計算に使える。 1 + read_int ();; 23 [Enter] 1.2 +. read_float ();; 4.3 [Enter] ちなみに試して分かっただろうが、read_line はそのままの文字列を返す。 次にchannelの説明に移る。 まずは一番単純なテキストファイルの読み書きから始める。 まずout_channelを開く。次のようにする。 let out = open_out "hoge";; この命令はテキストファイルを書き込むための命令で、すでにファイルが存在したときに はそれを空にする。つまり常に全く内容のないファイルを開くことになる。内容を付け足したい ときなどは後述のopen_out_genを使う。 out_channelに書き込むには output_char out 'a';; output_string out "bbb";; flush out;; などとする。最後には close_out out;; としてchannelを閉じる。次にこの内容を読み込む。 まずはin_channelを開く。 let input = open_in "hoge";; 次にその内容を読み込む。 input_char input;; input_line input;; などとする。最後にはin_channelを閉じる。 close_in input;; 名前を見れば想像つくだろうが、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";; Printf.fprintf out "test1\ntest2\n %d,%f" 10 10.2;; close_out out;; let input = open_in "hoge";; input_line input;; input_line input;; input_line input;; close_in input;; ところで、上の例ではinput_lineをひたすら手作業で繰り返したが、これでは行数が分からない テキストデータを読むことはできない。そこでテキストデータを1行ずつ読み込み、表示する関数を 書いてみよう。 let get_text filename = let input = open_in filename in try while true do print_endline (input_line input) done with End_of_file -> ();; get_text "hoge";; 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 = open_in_gen: - : open_flag list -> int -> string -> in_channel = 見ての通り返す値が違うだけでとる値は同じだ。三番目の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 とでもすればよい。言ったように、これは作成マスクによっては違う結果になるが、とりあえず 試してみよう。 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";; #load "unix.cma";; Unix.system "ls -l";; 結果はどうなっただろうか?自分の環境では -rw-rw-r-- 1 radio radio 0 1月 18 11:29 hoge1 -rw-r--r-- 1 radio radio 0 1月 18 11:30 hoge2 -rw------- 1 radio radio 0 1月 18 11:30 hoge3 となった。 とりあえず開いたchannelを閉じておく。 close_out out1;; close_out out2;; close_out out3;; 次にopen_flag_listの説明をする。上の例でなんとなく想像つくだろうが、これはファイルを開くときの いろいろな属性を指定する。open_flagは次のものがありこれらを必要なだけリストにしてopen_out_gen, open_in_genに渡す。 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";; output_string out "test1";; close_out out;; let out = open_out "hoge";; output_string out "test2";; close_out out;; let input = open_in "hoge";; input_line input;; close_in input;; test1という文字列が消されてしまっていることを確認できるはずだ。次に let out = open_out_gen [Open_wronly; Open_append; Open_text] 0 "hoge";; output_string out "test3";; close_out out;; let input = open_in "hoge";; input_line input;; close_in input;; こんどはtest3という文字列が付け加えられているはずだ。 次にユーザー定義のデータ型などバイナリでファイルに書き込む方法をみる。 バイナリで書き込むにはもちろんopen_out_binなどを使っても良いのだが、 output_valueとinput_value関数を利用すれば簡単にすむことが多い。 let out = open_out "hoge";; type complex = { re:int; im:int };; output_value out {re=1;im=3};; output_value out "test";; close_out out;; let input = open_in "hoge";; (input_value input: complex);; (input_value input: string);; close_in input;; きちんと取り出せているはずだ。このようにoutput_valueとinput_valueを 使うとバイナリデータが非常に簡単に扱えるが、input_valueを使うときに はユーザープログラムが明示的に型を指示してやらなくてはいけない。 次に配列、リストの簡単な使い方を説明する。 まずは配列から。 配列は次のような形で直接定義できる。 let a = [| 1;2;3;4 |];; 要素の型は何でも良いが、すべて等しい型でなくてはいけない。つぎのような のはエラー。 let a = [|1;2;'a'|];; 上のように直接作る他に関数で数を指定して作ることもできる。Arrayモジュールのmake という関数を使う。 Array.make 2 3;; Array.make 2 'a';; 一つめの引数が配列の要素数。2つめの引数がその初期値。 makeと同じ働きをするものとしてcreateというものがある。 Array.create 2 3;; Array.create 2 'a';; が推奨されていないのでできるだけmakeを使うべきだ。 makeでは配列の初期値はすべて一定だったが、それぞれ異なる値を初期値 にすることもできる。これにはinitという関数を使う。次のように使う。 Array.init 5 (fun i -> i+1);; Array.init は次の型を持つ。 - : int -> (int -> 'a) -> 'a array = 一個目のint型の引数が配列の要素数。2番目の引数の関数が初期値を決める関数だ。 この関数は要素の添字を引数にとる。いくつか例を挙げる。 Array.init 10 (fun i -> i*i);; Array.init 10 (fun i -> char_of_int (i+60));; Array.init 10 (fun i -> Random.float 1000.0);; 配列の内容を得るにはget関数を使うか、かっこで指定する。 let a = Array.init 10 (fun i ->i);; Array.get a 2;; Array.get a 3;; Array.get a 8;; 見れば分かるだろうが、一つめの引数で配列を、2番目の引数でどの要素を取り出すかを 指定する。しかし、実際にはこのように書かれることはほとんどなく次のように書かれること が多い。 a.(2);; a.(3);; a.(8);; 配列の要素の値を変更するにはsetを使うか、かっこで指定する。 Array.set a 4 14;; Array.set a 5 15;; Array.set a 8 18;; 次のように打つことで値が変っていることを確認できる。 a;; しかし、これも実際にわざわざこう書くことは少なくつぎのように書くことが多い。 a.(4) <- 4;; a.(5) <- 5;; a.(8) <- 8;; a;; 配列は値を取り出したり変更したりするものの他にもいくつかの関数が用意されている。 let a = [|1;2;3;4|];; let b = [|5;6;7;8|];; let c = [|9;10;11|];; Array.make_matrix 3 3 5;; 2次元配列をつくる。 Array.length a;; 配列の長さを返す。 Array.append a b;; 2つの配列の要素をつなげた配列を新しく作り返す。 Array.concat [a;b;c];; 配列のリストをうけとりそれらをすべてつなげる。 Array.sub a 1 2;; 配列の部分配列を返す。 Array.copy a;; 引数で指定した配列と同じ内容の配列を返す。 Array.fill a 1 2 10;; 引数で指定した範囲を3番目の引数の値で埋める。 Array.to_list a;; 配列をリストにしたものを返す。 Array.of_list [1;2;3;4];; リストを配列にしたものを返す。 Array.sort (fun a b -> if a=b then 0 else if a if a=b then 0 else if a if a=b then 0 else if a Printf.printf "%d," a) a;; Array.map (fun a -> Printf.printf "%d," a;a) a;; Array.iteri (fun i a -> Printf.printf "%d=%d," i a) a;; Array.mapi (fun i a -> Printf.printf "%d=%d," i a;a) a;; flod_left,fold_right: いわゆる畳み込み関数。 Array.fold_left (+) 0 [|1;2;3;4;5;6|];; Array.fold_right (+) [|1;2;3;4;5;6|] 0;; この2つの例は配列の要素の総和をだす。 リスト リストは次のような形で直接定義できる。 let a = [1;2;3;4];; リストに要素を付け加えるには 0::[1;2;3;4];; などとする。 2つのリストをひとつにするには [1;2;3;4] @ [5;6;7;8];; リスト操作の代表的なものとして以下のものがある。 let a = [1;2;3;4];; let b = [5;6;7;8];; let c = [9;10;11];; List.length a;; リストの長さを返す。 List.hd a;; リストの先頭要素を返す。 List.tl a;; リストの先頭要素以外を返す。 List.rev a;; 逆順のリストを返す。 List.nth a 2;; リストの2番目の引数の場所の要素を返す。 List.concat [a;b;c];; リストのリストをとってそれぞれをつなげる。 iter,map,iter2,map2 配列のものと基本的な役割は同じ。関数をリストの各要素に適用する。iter2,map2は 2つのリストの要素をを引数にする関数をとる。 List.iter (fun a -> Printf.printf "%d," a) a;; List.map (fun a -> Printf.printf "%d,"a;a) a;; List.iter2 (fun a b -> Printf.printf "1th=%d,2th=%d:" a b) a b;; List.map2 (fun a b -> Printf.printf "1th=%d,2th=%d:" a b;a) a b;; fold_left,fold_right,fold_left2,fold_right2: 配列のものと基本的な役割は同じ。 List.fold_left (+) 0 a;; List.fold_right (+) a 0;; List.fold_left2 (fun a b c -> a+b+c) 0 a b;; List.fold_right2 (fun a b c -> a+b+c) a b 0;; for_all,exists,for_all2,exists2: リストの要素それぞれに対して真偽を返す関数をとり、そのand、もしくはor を返す。例を挙げる。 リストの要素がすべて0以上であれば真を返す。 let func1 l = List.for_all (fun a -> if a>=0 then true else false) l;; func1 [~-2;4];; func1 [2;3];; func1 [32;4;5;6;0];; リストの要素で0以上のものがあれば真を返す。 let func2 l = List.exists (fun a -> if a>=0 then true else false) l;; func2 [~-2;4];; func2 [~-3;~-3];; func3 [~-4;0];; l1の要素がすべてl2の要素より大きければ真を返す。 let func3 l1 l2 = List.for_all2 (fun a b -> if a>b then true else false) l1 l2;; func3 [1;2;3;4] [0;2;0;0];; func3 [2;3;6;7] [1;2;3;5];; l1の要素でl2の要素より大きなものがあったとき真を返す。 let func4 l1 l2 = List.exists2 (fun a b -> if a>b then true else false) l1 l2;; func4 [1;2;3;4] [0;3;4;5];; func4 [1;2;3;4] [2;3;4;5];; mem,memq: リストの要素に含まれるかをどうかを返す。memqは==で判定し、memは=で判定する。 List.mem 2 [1;2;3];; List.mem 0 [1;2;3];; List.mem "a" ["a";"b";"c"];; List.memq "a" ["a";"b";"c"];; find,find_all,partition: リストの各要素に対して条件を満たしているものを返す。 リストの要素の中で0より大きい最初の要素を返す。 let func5 l = List.find (fun a -> if a>0 then true else false) l;; func5 [~-32;~-4;0;3;4;5];; func5 [1;~-2;3;4];; リストの要素の中で0より大きい要素をリストにして返す。List.find_allというのを List.filterとしても同じ。 let func6 l = List.find_all (fun a -> if a>0 then true else false) l;; func6 [~-32;~-4;0;3;4;5];; func6 [1;~-2;3;4];; 条件を満たすものと満たさないものをタプルにして返す。 let func7 l = List.partition (fun a -> if a>0 then true else false) l;; func7 [~-32;~-4;0;3;4;5];; func7 [1;~-2;3;4];; ソート: 配列のものとほとんど同じ。比較関数を定義して使う。 List.sort (fun a b -> if a=b then 0 else if a>b then 1 else ~-1) [1;3;2;1];; List.stable_sort (fun a b -> if a=b then 0 else if a>b then 1 else ~-1) [1;3;2;1];; List.fast_sort (fun a b -> if a=b then 0 else if a>b then 1 else ~-1) [1;3;2;1];; リストをマージする。マージされるリストは第一引数の比較関数でソートされていることを 前提とする。 let comp a b = if a=b then 0 else if a>b then 1 else ~-1;; let func8 l = List.sort comp l;; List.merge comp (func8 [1;3;1;~-3]) (func8 [6;2;34;3]);; Unixモジュール Unixモジュールの機能の一部を簡単に紹介する。 unixモジュールの紹介に入る前にラベルについて説明する。ラベルは関数の引数に名前をつけること が出来る機能でプログラムを読みやすくしたり、一部の引数だけを指定すればよいようにしたいとき に使う。 まず単純なラベル付き引数の例を挙げる。 let f ~x ~y = x + y;; これは次のいずれでも呼び出せる。 f 2 3;; f ~x:2 ~y:3 let x = 2 and y = 3 in f ~x ~y;; ラベルのあとに変数名を指定しなければラベルと同じ変数名になる。変数名を指定するには 次のようにする。 let f ~x:x1 ~y:y1 = x1 + y1;; これも次のいずれでも呼び出せる。 f 2 3;; f ~x:2 ~y:3 let x = 2 and y = 3 in f ~x ~y;; これだけだと何の意味があるのか分かりにくいと思うが、ラベルは次のようにすると デフォルト値を設定できる。 let f ?(x=1) y = x + y;; let f 2;; let f 4;; let f ~x:4 3;; let f ~x:6 2;; ところで全ての値をこのようにオプション引数にすることは出来るだろうか? 次のように打ち込んでみてほしい。 let f ?(x=1) ?(y=1) = x + y;; 一応関数が定義されるが警告が出たはずだ。これを避けるには最後に unit型を付け加える。 let f ?(x=1) ?(y=1) () = x + y;; f ~x:2 ~y:4 ();; f ~y:4 ();; f ();; f ~x:2 ();; unixモジュールには機能的には同等だが、ラベル付きのものとラベルなしのものが用意されて いる。 使うには #load "unix.cma";; module Unix = UnixLabels;; とするようだが、意味も良く分からないし面倒くさいのでここでは普通のモジュールを使う。 (lablglなどを使うときにはこの機能が非情に便利) socketを使った通信を試してみよう。ほとんどC言語と同じだ。 まずドメイン名からIPアドレスを表示する関数を作ってみよう。 まず名前からホスト名などの情報を表示するにはgethostbynameを使う。 let func1 name = Unix.gethostbyname name;; 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 = [|; ; |]} この中でh_addr_listがIPアドレスを格納している。google.co.jpは3つの IPアドレスを持っていることが分かる。これを表示するにはstring_of_inet_addr を使う。 let get_ip name = let host_entry = Unix.gethostbyname name in Array.map Unix.string_of_inet_addr host_entry.Unix.h_addr_list;; これでドメイン名からIPアドレスをリストにして返す関数が出来た。ここで注意してほしいのは h_addr_listの前にUnixと付けること忘れないようにすることだ。openを使わない限り、これは 必須になる。unixモジュールとの名前の一致も少ないだろうからopenを使ってしまっても良いかも しれない。 get_ip "google.co.jp";; - : string array = [|"216.239.57.104"; "216.239.59.104"; "216.239.39.104"|] 一応通信が出来ることが出来ることを確かめたので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。sock_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 val connect: file_descr -> sockaddr -> unit val listen: file_descr -> int -> unit Cと同じ。 他の定義に関してはManualを読んでほしい。 ソケット通信の簡単な例としてHTTP1.0を使った通信のプログラムを 作ってみる。 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_string (input_line a); print_newline(); done with End_of_file -> close_in a;; let str = "GET /index.html HTTP/1.0\r\n\r\n" in let sock = connect "caml.inria.fr" 80 in get str sock;; もうひとつソケット通信を使った簡単な例としてrubyプログラムとUNIXドメインを使って通信させてみる。 rubyプログラム require 'socket' $path = "/tmp/socket" sock = UNIXServer.open($path) s1 = sock.accept p s1.recvfrom(100) s1.close ocamlプログラム 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";; このプログラムの使い方はrubyのファイル名をunix_test.rbとすると、まず これを起動させる。シェルから次のように打ち込む。 ruby unix_test.rb そのあとにocamlインタプリタから pfunix_test "/tmp/socket" "test string";; などと打ち込む。渡す文字列は任意だが、ファイル名は決めうちになっているので、 最初(rubyプログラムを起動させる前)に/tmp/socketというファイルがあるとエラーになる。 Unixモジュールの最後の例としてパイプを使ってrubyプログラムの結果をocamlプログラム で受け取るプログラムを作ってみよう。次のようなrubyプログラムをインタプリタが起動している ディレクトリに用意する。これを hello.rbとする。 print "Hello\n" print "Hello\n" print "Hello\n" 見て通り3行にわたってHelloと表示するだけのプログラムだ。これをstring型のリストとして 受け取る関数を考える。 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言語に詳しい人ならもっときれいにできるのだろうが次のようにすると一応結果を受け取れていること が分かる。 pipe_test "ruby hello.rb";; - : string list = ["Hello"; "Hello"; "Hello"] # - : string list = []