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