第 7 章 参照,例外処理,入出力 83
7.6 練習問題
if false then print_string "a"; print_string "b" else ();;
は文法エラーになってしまう.(;まで読んだ時点でif式が終わったと判断してしまうためで先にあ るelse はみてくれない.)
分かりにくいのは,if など複数のキーワードから構成され,同じ優先度を持つ式が入れ子になっ た場合である.基本的には Objective Caml の文法で let, if, match, try, function, fun のよう に,終端を示すキーワードがないものは,できる限り先まで含めて式の一部であるように読まれる.
例えば,
if a then if b then c else d は
if a then (if b then c else d)
のことである.内側の if が右にできる限り伸びようとしているのがわかるだろう.
また,match の入れ子,練習問題にあるmatchと tryの組合わせは要注意である.
match e with
A -> match e’ with B -> ... | C -> ...
| D -> ...
はDの分岐も内側のmatchの一部として考えられてしまうので,Dが外側のmatchであるようにす るには以下のように括弧が必要である.
match e with
A -> (match e’ with B -> ... | C -> ...)
| D -> ...
練習問題の try式も括弧がないと最後の| _ ->以降がtry式の一部と見なされてしまうのである.
第4週の優先順位の表と上のルールで,一見して明らかでない部分式の結合はわかるはずである.
(プログラムが見にくくならない程度に括弧をつけるのもよい習慣である.)
7.6 練習問題
Exercise 7.1 ref型の値の表示を見て気づいている人もいるかもしれないが,ref型は以下のよう に定義された1フィールドの更新可能なレコードである.
type ’a ref = { mutable contents : ’a };;
関数 ref,前置オペレータ !,中置オペレータ :=の定義を,レコードに関連した操作で書け.
Exercise 7.2 与えられた参照の指す先の整数を1増やす関数incr を定義せよ.
# let x = ref 3;;
val x : int ref = {contents = 3}
# incr x;;
- : unit = ()
# !x;;
- : int = 4
Exercise 7.3 以下で定義するfunny_factは再帰的定義(rec)を使わずに階乗を計算している.ど のような仕組みで実現されているか説明せよ.
# let f = ref (fun y -> y + 1)
let funny_fact x = if x = 1 then 1 else x * (!f (x - 1));;
val f : (int -> int) ref = {contents = <fun>}
val funny_fact : int -> int = <fun>
# f := funny_fact;;
- : unit = ()
# funny_fact 5;;
- : int = 120
Exercise 7.4 参照を使って階乗関数を定義してみよう....部分を埋めよ.
let fact_imp n =
let i = ref n and res = ref 1 in while (...) do
...;
i := !i - 1 done;
...;;
Exercise 7.5 階乗関数fact を負の引数に対してInvalid_argument を発生させるように改良せよ.
Exercise 7.6 7.1.4節で述べた[] への参照の例を実際にインタラクティブ・コンパイラで試し,テ キストに書いた挙動との違い,特に,参照の型を説明し,どのようにしてtrue を[1]にcons して しまうような事態の発生が防がれているか説明せよ.
Exercise 7.7 上でみたオブジェクト指向風プログラミングの延長で継承などを表現してみようと思
う.以下は色を表す型と色付き点オブジェクトのインターフェースである.
# type color = Blue | Red | Green | White;;
type color = Blue | Red | Green | White
# type cpointI = {cget: unit -> int;
cset: int -> unit;
cinc: unit->unit;
getcolor: unit-> color};;
type cpointI = { cget : unit -> int;
cset : int -> unit;
cinc : unit -> unit;
getcolor : unit -> color;
}
色付き点オブジェクトは座標に加え,色を状態としてもつとする.また,cget,cinc は pointC の メソッドを継承し,cset は座標のセットとともに色を白にセットするように実装したい.以下がそ の試みである.
# let cpointC x col=
let super = pointC x in let rec this =
7.6. 練習問題 97
{cget= super.get;
cset= (fun x -> super.set x; col := White);
cinc= super.inc;
getcolor = (fun () -> !col)} in this;;
val cpointC : int ref -> color ref -> cpointI = <fun>
# let new_cpoint x col = cpointC (ref x) (ref col);;
val new_cpoint : int -> color -> cpointI = <fun>
しかし,この実装はうまく働かない.
# let cp = new_cpoint 0 Red;;
val cp : cpointI =
{cget = <fun>; cset = <fun>; cinc = <fun>; getcolor = <fun>}
# cp.cinc();;
- : unit = ()
# cp.cget();;
- : int = 1
# cp.getcolor();;
- : color = Red
cinc 中では,座標をセットするので色は白になっていてほしいのに元のままである.この理由をプ ログラムの挙動とともに説明し,うまく cinc が作動するようにプログラムを書き換えよ.ただし,
継承を模倣したいので,同じことをするメソッドに相当する関数は一度書くだけで(上の super.get のような形で)再利用すること.
(ヒント: pointCの定義を以下のように変更する.)
# let pointC x this () = {get= (fun () -> !x);
set= (fun newx -> x:=newx);
inc= (fun () -> (this ()).set ((this ()).get () + 1))};;
val pointC : int ref -> (unit -> pointI) -> unit -> pointI = <fun>
# let new_point x = let x = ref x in
let rec this () = pointC x this () in this ();;
val new_point : int -> pointI = <fun>
Exercise 7.8 以下の関数changeは,お金を「くずす」関数である.
# let rec change = function (_, 0) -> []
| ((c :: rest) as coins, total) ->
if c > total then change (rest, total) else c :: change (coins, total - c);;
Warning P: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
([], 1)
...function (_, 0) -> []
| ((c :: rest) as coins, total) ->
if c > total then change (rest, total)
else c :: change (coins, total - c)..
val change : int list * int -> int list = <fun>
与えられた(降順にならんだ)通貨のリスト coinsと合計金額 totalからコインのリストを返す.
# let us_coins = [25; 10; 5; 1]
and gb_coins = [50; 20; 10; 5; 2; 1];;
val us_coins : int list = [25; 10; 5; 1]
val gb_coins : int list = [50; 20; 10; 5; 2; 1]
# change (gb_coins, 43);;
- : int list = [20; 20; 2; 1]
# change (us_coins, 43);;
- : int list = [25; 10; 5; 1; 1; 1]
しかし,この定義は先頭にあるコインをできる限り使おうとするため,可能なコインの組合わせがあ るときにでも失敗してしまうことがある.
# change ([5; 2], 16);;
Exception: Match_failure ("", 201, 17).
これを,例外処理を用いて解がある場合には出力するようにしたい.以下のプログラムの,2個所の ...部分を埋め,プログラムの説明を行え.
let rec change = function (_, 0) -> []
| ((c :: rest) as coins, total) ->
if c > total then change (rest, total) else
(try
c :: change (coins, total - c) with Failure "change" -> ...)
| _ -> ...;;
Exercise 7.9 print_int 関数を stdout,output_stringなどを用いて定義せよ.
Exercise 7.10 二つのファイル名を引数にとって,片方のファイルの内容をもう片方にコピーする
関数 cp を定義せよ.とくに一度開いたファイルは最後に閉じることを忘れないように.
99