open宣言

モジュールの関数を使うときには[モジュール名].[関数名]とするけど,open宣言をすればモジュール名をつけなくても使えるようになる。

 # map (fun x -> x * x) [1;2;3;4];;
 Characters 0-3:
   map (fun x -> x * x) [1;2;3;4];;
   ^^^
 Unbound value map
 # open List;;
 # map (fun x -> x * x) [1;2;3;4];;
 - : int list = [1; 4; 9; 16]

複数のモジュールをopenしたとき,同じ名前の関数がある場合には後からopenしたモジュールの関数だけが使えるようになる。たとえば,map関数は ListモジュールにもArrayモジュールにもあるけど,次のようにするとArrayモジュールのほうだけが使える。

 # open List;;
 # open Array;;
 # map (fun x -> x * 10) [|1;2;3;4|];;
 - : int array = [|10; 20; 30; 40|]

↑Arrayのmapは使える。↓Listのmapは使えない。

 # map (fun x -> x * 10) [1;2;3;4];;
 Characters 22-31:
   map (fun x -> x * 10) [1;2;3;4];;
                         ^^^^^^^^^
 This expression has type 'a list but is here used with type int array

モジュール名をつけてやれば使える。

 # List.map (fun x -> x * 10) [1;2;3;4];;
 - : int list = [10; 20; 30; 40]

練習問題8.1

本文中でもふれたように,ref型は以下のように定義された,1フィールドの書き換え可能な レコードです。

type 'a ref = { mutable contents : 'a };; 
関数 ref,前置演算子 !,中置演算子 := の定義をレコードに関連した操作で書きなさい。

こうかな。

 let ref x = { contents = x };;
 let (!) x = x.contents;;
 let (:=) x v = x.contents <- v;;

練習問題8.4

参照と繰り返しの構文(while,for)を使ってフィボナッチ数を求める関数を定義しなさい。

 # let fib n =
     let fibs = ref (1, 1) in
     let i = ref 1 in
     while !i < n do
       fibs := (snd !fibs, fst !fibs + snd !fibs);
       i := !i + 1
     done;
     snd !fibs
   ;;
 val fib : int -> int = <fun>

初項を第0項とした。

 # fib 0;;
 - : int = 1
 # fib 1;;
 - : int = 1
 # fib 2;;
 - : int = 2
 # fib 3;;
 - : int = 3
 # fib 4;;
 - : int = 5
 # fib 5;;
 - : int = 8
 # fib 6;;
 - : int = 13

チャネルを使った入出力

チャネルっていうのは,ファイルディスクリプタみたいなものだと思っておけば良さそう。
入力用には open_in と close_in を使う。
こういうファイル members.txt があったとして:

 ^o^ >type members.txt
 andy
 bill
 charlie

ファイルから入力する例。

 # let infile = open_in "C:/home/takatoh/members.txt";;
 val infile : in_channel = <abstr>
 # input_line infile;;
 - : string = "andy"
 # input_line infile;;
 - : string = "bill"
 # input_char infile;;
 - : char = 'c'
 # input_byte infile;;
 - : int = 104

ファイルの最後に到達すると End_of_file 例外が発生する。

 # input_line infile;;
 - : string = "arlie"
 # input_line infile;;
 Exception: End_of_file.
 # close_in infile;;
 - : unit = ()


一方,ファイルに出力するには open_out と close_out。

 # let outfile = open_out "C:/home/takatoh/foo.txt";;
 val outfile : out_channel = <abstr>
 # output_string outfile "Hello, world.";;
 - : unit = ()
 # output_char outfile '\n';;
 - : unit = ()
 # close_out outfile;;
 - : unit = ()

できたファイル:

 ^o^ >type foo.txt
 Hello, world.


まとめ

  入力 出力
オープン open_in open_out
クローズ close_in close_out
1行ずつ input_line output_string
1文字ずつ input_char output_char
1バイトずつ input_byte output_byte

ファイルに追加出力する

open_out を使ってファイルを開くと,そのファイルがすでに存在した場合,中身を消去してしまう。既存のファイルに追加するには,open_out_gen を使ってチャネルを作る*1

 # let outfile = open_out_gen [Open_wronly; Open_append; Open_text] 0o666 "C:/home/takatoh/foo.txt";;
 val outfile : out_channel = <abstr>

第1引数は open_flag といって,ファイルを開くときのオプション。上で設定してるのはライトオンリー,追加,テキストファイル,というところだろう。第2引数はファイルのパーミッション,第3引数はファイル名。で,かえってくるのは open_out と同じ出力用のチャネル(out_channel)。

 # output_string outfile "foo\n";;
 - : unit = ()
 # output_string outfile "bar\n";;
 - : unit = ()
 # close_out outfile;;
 - : unit = ()
 # open_out_gen;;
 - : open_flag list -> int -> string -> out_channel = <fun>

foo.txt はこうなる。

 ^o^ >type foo.txt
 Hello, world.
 foo
 bar

ちゃんと追加になっている。


open_flag はヴァリアントで,The Objective Caml system release 3.1019.2 Module Pervasives: the initially opened module の General output functionsの項によると:

 type open_flag =
 | 	Open_rdonly 	(*	open for reading.	*)
 | 	Open_wronly 	(*	open for writing.	*)
 | 	Open_append 	(*	open for appending: always write at end of file.	 *)
 | 	Open_creat 	(*	create the file if it does not exist.	*)
 | 	Open_trunc 	(*	empty the file if it already exists.	*)
 | 	Open_excl 	(*	fail if Open_creat and the file already exists.	*)
 | 	Open_binary 	(*	open in binary mode (no conversion).	*)
 | 	Open_text 	(*	open in text mode (may perform conversions).	*)
 | 	Open_nonblock 	(*	open in non-blocking mode.	*)

*1:ここ,「プログラム in OCaml」に誤植あり。○ Open_wronly,× Open_wonly

繰り返しのための高階関数

繰り返しの構造を関数(再帰関数)にすることもできる。

 # let rec whle condition body =
     if condition () then
       begin body (); whle condition body end
   ;;
 val whle : (unit -> bool) -> (unit -> 'a) -> unit = <fun>

condition () が真であるあいだ body を繰り返す。条件 condition () が単なる真偽値の式ではなくて unit -> bool 型の関数なところがミソ。たとえばファイルからの入力など。

もう一つ,リストの各要素に対して繰り返す関数。

 # let rec iter f = function
     [] -> ()
   | a :: rest -> begin f a; iter f rest end
   ;;
 val iter : ('a -> 'b) -> 'a list -> unit = <fun>

リストの各要素を出力するときとかに使える。

制御構造

入出力など,副作用のある計算をするときには式を評価する順番が重要になる。OCaml にもそのための制御構造(control structure)がある。

逐次実行

1つの方法は let 〜 in を使うこと。let 以下が評価された後に in 以下が評価される。

 # let () = print_string "Hello, " in
     print_string "world.\n";;
 Hello, world.
 - : unit = ()

複数の式を ; で区切って書くと左から実行する。全体の値はいちばん右の式の値。途中の式の値は捨てられる。

 # print_string "Hello, "; print_string "world.\n";;
 Hello, world.
 - : unit = ()

条件分岐

if をつかう。then 節が unit型の式であるときに限って,else 以下を省略できる。

 # if true then print_string "Hello, world.\n";;
 Hello, world.
 - : unit = ()

これは,条件が偽なら何もしない,ということ。

 # if false then print_string "Hello, world.\n";;
 - : unit = ()

begin 〜 end

; と if では if のほうが結合強度が強く,then節や else節の途中で ; が出てくるとそこでif式全体が終わりだと判断される。

 # let f b = if b then print_string "Hello, "; print_string "world.\n";;
 val f : bool -> unit = <fun>

この関数は引数(=ifの条件)が偽なら "world.\n" だけが出力される(引数に関係ないから)。

 # f true;;
 Hello, world.
 - : unit = ()
 # f false;;
 world.
 - : unit = ()

もし,真の時に "Hello, world\n" を出力し,偽の時には何もしたくないなら括弧で囲む。

 # let f2 b = if b then (print_string "Hello, "; print_string "world.\n");;
 val f2 : bool -> unit = <fun>
 # f2 true;;
 Hello, world.
 - : unit = ()
 # f2 false;;
 - : unit = ()

または括弧の代わりにbegin 〜 endをつかう。こっちのほうが「よいスタイル」だと推奨されているらしい。

 # let f3 b = if b then begin print_string "Hello, "; print_string "world.\n" end;;
 val f3 : bool -> unit = <fun>
 # f3 true;;
 Hello, world.
 - : unit = ()
 # f3 false;;
 - : unit = ()

繰り返し

while は

 while [式1] do [式2] done

という形をしていて,式1が真であるあいだ式2を繰り返す。while を使った fact の例:

 # let fact n =
     let i = ref 1 and res = ref 1 in
     while (!i <= n) do
       res := !res * !i; i := !i + 1
     done;
     !res
   ;;
 val fact : int -> int = <fun>
 # fact 5;;
 - : int = 120

for は

 for [変数] = [式1] to [式2] do [式3] done

または

 for [変数] = [式1] downto [式2] do [式3] done

という形をしていて,[変数]を整数[式1]から[式2]まで順に束縛しながら[式3]を評価する。for を使って fact を定義してみよう。

 # let fact2 n =
     let res = ref 1 in
     for i = 1 to n do
       res := !res * i
     done;
     !res
   ;;
 val fact2 : int -> int = <fun>
 # fact2 5;;
 - : int = 120