Transcript PPT

ML演習 第 3 回
2005/06/14
末永 幸平, 遠藤 侑介, 大山 恵弘
今日の内容
例外
 副作用を利用したプログラミング





Reference
代入可能フィールド
Value restriction
等値演算子と比較演算子
2
今日の内容
例外
 副作用を利用したプログラミング





Reference
代入可能フィールド
Value restriction
等値演算子と比較演算子
3
例外処理とは?

異常が発生したときに, 現在の計算を中断して
別の処理を始めること






開こうとしているファイルが見つからない
配列の境界を越えてアクセスした
ユーザの入力がおかしい
関数に渡されるべきでない値が渡された
...
OCaml には例外処理機構が組み込まれている
4
例題

ベクトル操作を行う関数を実装

normalize : float * float -> float * float


angle : float * float -> float * float -> float


ベクトルの正規化を行う
ベクトル同士のなす角をラジアンで返す
angle_deg : float * float -> float * float -> float
ベクトル同士のなす角を degree で返す
 ラジアンから degree への変換関数
(degree_of_radian) は既に与えられているものとする

5
OCaml の例外処理

例: ベクトルの正規化
# let normalize (x, y) =
let n = sqrt (x *. x +. y *. y) in
(x /. n, y /. n);;
val normalize :
float * float -> float * float = <fun>
# normalize (3.0, 4.0);;
- : float * float = (0.6, 0.8)
 normalize に (0.0, 0.0) が与えられたときは
どうする?
6
angle と angle_deg の実装
# let angle v1 v2 =
let ((x, y), (x’, y’)) =
(normalize v1, normalize v2)
in acos(x *. x’ +. y *. y’);;
val angle : float * float ->
float * float -> float = <fun>
# let angle_deg v1 v2 =
degree_of_radian (angle v1 v2);;
val angle_deg : float * float ->
float * float -> float = <fun>
 normalize に (0.0, 0.0) を与えたときは
どうする?
7
option 型を使った解決法 (1)

(0, 0) が与えられたら None を返す
# let normalize (x, y) =
let n = sqrt (x *. x +. y *. y) in
if n = 0.0 then None
else Some(x /. n, y /. n);;
val normalize :
float * float ->
(float * float) option = <fun>
# normalize (0.0, 0.0);;
- : (float * float) option = None
8
option 型を使った解決法 (2)

この方法の欠点: 使いづらい!!
毎回パターンマッチ
が必要
# let angle v1 v2 =
match (normalize v1, normalize v2) with
(None, _) | (_, None) -> None
| (Some(x, y), Some(x’, y’)) ->
Some(acos(x *. x’ +. y *. y’));;
val angle : float * float ->
float * float -> float option = <fun>
# let angle_deg v1 v2 =
angle_deg でも
match angle v1 v2 with
パターンマッチ
None -> None
| Some x -> Some (degree_of_radian x);;
9
例外機構を使った解決法 (1)
例外の定義

(0, 0) が与えられたら例外を投げる
# exception ZeroVector;;
exception ZeroVector
例外の送出
# let normalize (x, y) =
let n = sqrt (x *. x +. y *. y) in
if n = 0.0 then raise ZeroVector
else (x /. n, y /. n);;
val normalize :
float * float -> float * float = <fun>
# normalize (3.0, 4.0);;
返り値は float の
- : float * float = 0.6, 0.8
ままでよい
# normalize (0.0, 0.0);;
Exception: ZeroVector.
10
例外機構を使った解決法 (2)
例外を使ったので
パターンマッチ不要
# let angle v1 v2 =
let ((x, y), (x’, y’)) =
(normalize v1, normalize v2)
in acos(x *. x’ +. y *. y’);;
val angle : float * float ->
float * float -> float = <fun> normalize の例外
が伝播する
# let angle_deg v1 v2 =
degree_of_radian (angle v1 v2);;
val angle_deg : float * float ->
float * float -> float = <fun>
# angle_deg (0.0, 0.0) (0.0, 0.5);;
Exception: ZeroVector.
11
例外機構を使った解決法 (3)

発生した例外を処理する
# let angle_str v1 v2 =
例外が起きたときに
try
どういう処理を行うか記述
“Angle is” ^
string_of_float (angle_deg v1 v2)
with ZeroVector -> “Not defined.”;;
val angle_str : float * float ->
float * float -> string = <fun>
# angle_str (3.0, 1.0) (1.0, 2.0);;
- : string = “Angle is 45.”
# angle_str (1.0, 0.5) (0.0, 0.0);;
- : string = “Not defined.”
12
例外のまとめ (1)

例外の定義


exception ZeroVector
exception BadArg of float


引数つきの例外も可能 (バリアントと同じ)
例外の送出


raise ZeroVector
raise (BadArg 3.0)
13
例外のまとめ (2)

例外のキャッチ (例外ハンドラの登録)



try … with ZeroVector -> …
try … with BadArg x -> …
ここで x が使える
with の後はパターンマッチになっている

try … with
ZeroVector -> … | BadArg x -> … | _ -> …
14
例外の応用

大域脱出
# exception Zero;;
# let prod l =
答えが 0 だと分かった
let rec f = function
時点で返る
[] -> 1
| hd::tl ->
if hd = 0 then raise Zero
else hd * f tl
in try f l with Zero -> 0;;
val prod : int list -> int = <fun>
# prod [1; 2; 3; 0; 7; 8; 0];;
- : int = 0
15
今日の内容
例外
 副作用を利用したプログラミング





Reference
代入可能フィールド
Value restriction
等値演算子と比較演算子
16
unit 型

() を唯一の値として持つ型
# ();;
- : unit = ()

用途

副作用以外に意味のない関数や式の返り値


reference への代入 (このあと説明) など
引数の不要な関数に与えるダミー値

C++ の void 型に相当
17
Reference

中身を変更できるセル (“箱”)
# let a = ref 0;; (* 0 で初期化した参照を作る *)
val a : int ref = {contents = 0}
# !a;; (* 参照先を取り出す *)
- : int = 0
# a := 5;; (* 参照に代入 *)
返り値は unit 型
- : unit = ()
# !a;; (* 再び参照先の値を取り出す *)
- : int = 5
18
Mutable Field

値を変更できる record 中のフィールド
# type mutable_point =
{ mutable x : int; mutable
type mutable_point =
{ mutable x : int; mutable
# let p1 = { x = 5; y = 3; };;
val p1 : mutable_point = { x =
# p1.x <- 6;;
- : unit = ()
# p1;;
- : mutable_point = { x = 6; y
y : int };;
y : int; }
5; y = 3 }
= 3 }
19
複文

複数の式を順に評価
# let increment x a = (x := !x + a ; !x);;
val increment : int ref -> int -> int = <fun>
# let a = ref 0;;
val a : int ref = {contents = 0}
# increment a 1;;
- : int = 1
# increment a 2;;
- : int = 3
最後の式の結果 (!x) が
全体の結果になる
20
Value restriction
 型多相と

reference は相性が悪い
(実際には存在しない) 例
# let r1 = ref [ ];;
val r1 : ’a list ref = { contents = <fun> }
# let f () = List.map not (!r1);;
val f : unit → bool = <fun>
# r1 := [5];;
- : unit = ()
# f ();;

どこがおかしい?
21
Value restriction: 原因はなんだ?
な参照に polymorphic で
ない値を代入したこと?
 Polymorphic

根本的な原因ではあるが, これを静的に
禁止するのは ML の型システムでは難しい
# let r1 = ref (fun x → x);; (* (’a → ’a) ref *)
# let set_r1 f x = let res = !r1 x in r1 := f; res;;
val set_r1 : (’a → ’a) → ’a → ’a = <fun>
(* 型だけ見ると, set_r1 に int → int などを渡しては
いけないということは分からない *)
22
Value restriction: 原因はなんだ?

(続き)
# set_r1 (fun x → x) 5;;
(* fun x → x は ’a → ’a なので通したい *)
- : int = 5
# set_r1 (fun x → x + 1) 5;;
(* fun x → x + 1 は int → int なので禁止したい *)
- : int = 6 (* . . . MLの型システムでは禁止できない *)
# set_r1 (fun x → x) true;;
(* fun x → x + 1 が true に適用されてしまう *)

結局、ML のシステムと整合を取って
参照に多相型を与えるのは難しい
23
Value restriction: とりあえずの解決策

reference には 「未決定の単相型」 を与える
# let r1 = ref [];;
val r1 : ’_a list ref = { contents = <fun> }
# let f1 () = List.map not !r1;;
’_a: 一旦型が確定
val f1 : unit -> bool list = <fun>
すると, それ以降は
# let _ = !r1 @ [5];;
多相的に使えない型
This expression has type int but is here used with type
bool
# !r1;;
- : bool list = []
24
Value restriction: まだ問題があるぞ

更なる問題: unit → ’a → ’a 型の値に
() を apply した結果の型は?
自然な fun () -> (fun x -> x) の場合を考えると
’a → ‘a としたい
 次の f の場合単相型関数 ’_a → ’_a しかだめ

let f () = let r = ref None in
fun x →
let old = match !r with
None → x | Some y → y
in r := Some x; old
25
Value restriction: 最終的な解決策

MLでの解決策: 副作用がないと確実にわかる
「値」 にのみ多相型を与える

結局のところ, 評価されうる式は副作用を持ちうるので



値とは, それ以上評価されることがない式
OK: 定数, fun 式, それらの tuple,
それらからなる変更不可データ構造
NG: reference, let 式, 関数適用 etc...
# (fun () x -> x) ();;
- : ’_a → ’_a = <fun>
26
Value restriction: 注意すべきこと

部分適用が単相型になることがある
# let f = List.map (fun x -> (x, x));;
(* 多相型になってほしいが, 値ではないので
なので単相型になってしまう *)
val f : ’_a list -> (’_a * ’_a) list = <fun>

解決策: η展開 (仮引数を明示する)
# let f xs = List.map (fun x -> (x, x)) xs;;
val f : ’a list -> (’a * ’a) list = <fun>
27
Value restriction: 参考文献

最初に Value restriction を提案した論文

Andrew K. Wright, Matthias Felleisen.
A Syntactic Approach to Type Soundness.
読みやすいので学部生にもおすすめ
 Value restriction 以外の解決法との得失比較あり


最近提案された OCaml の拡張

Jacques Garrigue. Relaxing Value Restriction.

OCaml 3.07 で採用
28
アドバイス

論文の探し方

基本的には Google


ACM Digital Library ( http://www.acm.org から )


タイトル, 著者名, 発表された会議や雑誌で検索
ACMの学会で発表された論文ならここにもある
CiteSeer.IST ( http://citeseer.ist.psu.edu/cis/ )
論文データベース
 Google で検索すると大抵ここの検索結果がかかる


著者の Web サイト
29
今日の内容
例外
 副作用を利用したプログラミング




Reference
代入可能フィールド
等値演算子と比較演算子
30
等値演算子 (1)

2 種類の等値演算子がある

= (否定: <>): 「構造的な一致」
複合データの中身まで探索
 Scheme の equal? に相当


より詳しい定義は
マニュアル参照
== (否定: !=): 「物理的な一致」
「アドレス」のみを見る
 Scheme の eq? に相当


== の方が識別力が強い

(x == y) が成り立てば (x = y) も成り立つ
31
等値演算子 (2)
# let test x y = (x = y, x == y);;
val test : ‘a -> ‘a -> bool * bool = <fun>
# test 1 1;;
- : bool * bool = true, true
# test 1.0 1.0;;
- : bool * bool = true, false
# test “string” “string”;;
- : bool * bool = true, false
float や string の定数は
別々の場所に確保される
ことがある
32
等値演算子 (3)
参照先が等しければ
参照同士も等しい
# test (ref 1) (ref 1);;
- : bool * bool = true, false
# let r = (ref 1) in test r r;;
異なる場所に確保された
- : bool * bool = true, true
値への参照なので false
# (fun x -> x) = (fun x -> x);;
Exception: Invalid_argument
“equal: functional value”
# (fun x -> x) == (fun x -> x);;
- : bool = false
# let f = (fun x -> x) in test f f;;
- : bool * bool = true, true
関数同士が内容的に等しいかど
うかは一般に決定不能
33
比較演算子

<, >, <=, >=

型: α → α → bool





何でも比べられる
= と対応した演算子
整数・実数 → 数値で比較
文字列・文字 → 辞書式順序で比較
その他のオブジェクト → 実装依存

注: 循環参照を持つデータでは止まらないことがある
34
第 3 回 課題
締め切り: 6/28 13:00
(しつこいようだが日本標準時)
課題 1 から 6

詳細は別紙参照
36
y
課題1 (例)
# let t1 = new_turtle ();;
val t1 : turtle = { … }
# advance t1 1.0; locate t1;;
- : float * float = (1., 0.)
# rotate t1 90.0; advance t1 1.0; locate t1;;
- : float * float = (1., 1.)
# rotate t1 90.0; advance t1 1.0; locate t1;;
- : float * float = (0., 1.)
# rotate t1 90.0; advance t1 1.0; locate t1;;
- : float * float = (0., 0.)
x
(*計算誤差で値が正確に0にならないのは気にしなくて良い*)
37
ICFP Programming Contest

過去の OCaml プログラムの実績

1998: 2位 (ENS Camlist, France)
1999: 1位 (Camls ’R Us, OCaml 作者グループ)
2000: 1位 (PLClub, U-Penn, 米澤研 住井, 細谷 参加)
2位 (Camls ’R Us)
2001: 入賞選外 (3位タイ)
2002: 1位 (TAPLAS, 米澤研: 大岩, 関口, 住井)
2003: 入賞選外

2004: 入賞選外 (1位から4位まで Haskell っておい)





38
レポート提出上の注意 (1)

提出方法: 電子メール

宛先: [email protected]


Subject を
Report <レポート番号> <学生証番号>
とすること



受領通知が届くと思うので確認のこと
今回の場合 “Report 3 510xx”
地下以外から提出する場合, 地下計算機の
アカウントを書くこと
レポートは添付ファイルにせず, メール本文に
記述すること
39
レポート提出上の注意 (2)

レポート本文に含めるべきもの


氏名, 学生証番号
ソース


動作例


コメントを適宜補い, 各関数の動作を説明すること
プログラムが正しく動作することを示すのに
ふさわしい例を考えること
考察
考察不要と指定されている場合を除き, 必ず入れる
 感想, 愚痴などは考察とはみなさない

40