オブジェクト指向機能
class 宣言
object ... end
- OCaml のインスタンス変数のメンバは全てプライベート
# class point ini_x ini_y =
object (self) (* 自身の名前をつける, 自由なので this とかでも良い *)
val mutable x = 0 (* インスタンス変数は外からアクセス不可 *)
val mutable y = 0
(*
* インスタンスメソッド
* method メソッド名 引数... = 式
*)
method set new_x new_y = begin x <- new_x; y <- new_y end
method private print_x = print_int x (* プライベートメソッド *)
(* コンストラクタ *)
initializer begin
x <- ini_x; y <- ini_y
end
end;;
(* シグネチャ *)
class point :
int ->
int ->
object
val mutable x : int
val mutable y : int
method private print_x : unit
method set : int -> int -> unit
end
インスタンスの生成
new クラス名
# let p = new point;;
val p : point = <obj>
インスタンスメソッドの呼び出し
インスタンス#メソッド名
# p#set 1 2;;
- : unit = ()
継承
inherit クラス名
以下のクラスは親クラスには super
自身には self
でアクセス可能。
(* 座標点をプリントする *)
# class point_with_print x y =
object (self)
inherit point x y as super (* 親クラスへのアクセス名 *)
method print = Printf.printf "(%d, %d)\n" x y
end;;
class point_with_print :
int ->
int ->
object
val mutable x : int
val mutable y : int
method print : unit
method private print_x : unit
method set : int -> int -> unit
end
(* 継承されたクラスのインスタンスを生成 *)
# let p = new point_with_print 1 1;;
val p : point_with_print = <obj>
# p#print;;
(1, 1)
- : unit = ()
クラスの型
<メソッド名: 型 ... >
- オブジェクトの型はメソッドの型を並べたもの
- メソッド名と型の組み合わせが合致すれば同じオブジェクト型とみなす
クラスの型を確認するには class
を使わず let
で直接オブジェクトを定義する。
(* オブジェクトを直接定義 *)
let obj =
object (self)
val mutable x = 0
val mutable y = 0
method set new_x new_y = begin x <- new_x; y <- new_y end
end;;
(* 型が表示される *)
val obj : < set : int -> int -> unit > = <obj>
(* インスタンスメソッドを呼び出してみる *)
# obj#set 1 2;;
- : unit = ()
上記の obj
の型は < set : int -> int -> unit >
つまり、 int -> int -> unit
なメソッド set
を持つという意味。
この定義を満たすメソッドを持つクラスなら、同じオブジェクト型とみなされる。
(* 上記 obj と無関係のクラスを定義 *)
# class unrelated_class =
object
(* x, y を表示するメソッド set を定義 *)
method set x y = Printf.printf "(%d, %d)\n" x y
end;;
class unrelated_class : object method set : int -> int -> unit end
(* オブジェクト型が一致するので同じリストに入れられる *)
# let obj2 = new unrelated_class;;
val obj2 : unrelated_class = <obj>
# [obj; obj2];;
- : unrelated_class list = [<obj>; <obj>]
(* オブジェクト型が一致するので同じ返り値型として扱える *)
# let hoge x = if x then obj else new unrelated_class;;
val hoge : bool -> unrelated_class = <fun>
# (hoge true)#set 1 2;;
- : unit = ()
# (hoge false)#set 1 2;;
(1, 2)
- : unit = ()
個人的には Java や Go の interface
が OCaml のオブジェクト型の役割をはたしている用に感じる。
部分型
部分型 => オブジェクト型のメソッドの一部を定義していたら部分型として扱う
例: 型2 は 型1 の部分型
型1 => <method1: int -> int, method2: unit>
型2 => <method1: int -> int>
コアーション(型変換)
(式:型1 :> 型2)
部分型型1
を型2
に変換する。Javaでいうアップキャストに近い。
コアーションで削ぎ落とされたメソッドは以降呼び出せない。
(* print_class1 は print_class2 の部分型 *)
# class print_class1 = object
method print_1 = print_int 1
end;;
class print_class1 : object method print_1 : unit end
# class print_class2 = object
method print_1 = print_int 1
method print_2 = print_int 2
end;;
class print_class2 : object method print_1 : unit method print_2 : unit end
(* オブジェクト型が異なるため print_class1, print_class2 は同じリストに入らない *)
# let obj_list = [new print_class1; new print_class2];;
Error: This expression has type print_class2
but an expression was expected of type print_class1
The second object type has no method print_2
(* コアーション(型変換)によって同じリストに入れる *)
# let obj_list = [new print_class1; (new print_class2 :> print_class1)];;
val obj_list : print_class1 list = [<obj>; <obj>]
(* コアーションによって削ぎ落とされた情報は呼び出せない *)
# let [obj1; obj2] = obj_list;;
val obj1 : print_class1 = <obj>
val obj2 : print_class1 = <obj>
# obj1#print_1;; (* 呼び出せる *)
1- : unit = ()
# obj2#print_2;; (* 呼び出せない *)
Error: This expression has type print_class1
It has no method print_2
多層的オブジェクト型
ある部分型を満たす任意の型を表す
-
<メソッド名1: 型1; ... ,メソッド名n: 型n; ..>
- 最後の 「..」 が大事
- 雑に言うと「その他いろいろ」ということを示す
(* 多層的オブジェクト型を受け取る関数を定義する *)
# let print1 print_obj = print_obj#print_1;;
val print1 : < print_1 : 'a; .. > -> 'a = <fun>
(* 部分型を満たすオブジェクト型を受け取れる *)
# print1 obj1;;
1- : unit = ()
# print1 obj2;;
1- : unit = ()
上の < print_1 : 'a; .. > -> 'a = <fun>
の -> 'a
の部分の'a
は 型変数< print_1 : 'a; .. >
の別名(?)
多層的オブジェクト型に与えられた引数の型の変化について
- 多層的な型を引数に与えた場合
(* 多層的関数を定義 *)
# let k1 a b = a and k2 a b = b;;
val k1 : 'a -> 'b -> 'a = <fun>
val k2 : 'a -> 'b -> 'b = <fun>
(* 同じ型を要求する関数の返り値にすると型が変化する *)
# let f b = if b then k1 else k2;;
(* 'a -> 'b -> 'a / 'b の型が 'a -> 'a -> 'a になっている *)
val f : bool -> 'a -> 'a -> 'a = <fun>
- 多層的ではない型を引数に与えた場合
(* 異なる型の関数にすると *)
# let k1' (a:int) (b:string) = a and k2' (a:int) (b:string) = b;;
val k1' : int -> string -> int = <fun>
val k2' : int -> string -> string = <fun>
(*
* 返り値の関数型が異なるので返り値に使えない
* 型の共通化を行うことができない
*)
# let f' b = if b then k1' else k2';;
Error: This expression has type int -> string -> string
but an expression was expected of type int -> string -> int
Type string is not compatible with type int
- 多層的なオブジェクト型を与えた場合
(* 多層的オブジェクト型の関数を定義する *)
# let print1' obj = obj#print_1 and print2' obj = obj#print_2;;
val print1' : < print_1 : 'a; .. > -> 'a = <fun>
val print2' : < print_2 : 'a; .. > -> 'a = <fun>
(* 2つの多層的オブジェクト型の関数をまとめると *)
# let f b = if b then print1' else print2';;
(*
* 関数の返り値の型が < print_1 : 'a; print_2 : 'a; .. >
* つまり, それぞれの多層的オブジェクト型を合成したような型がになる
*)
val f : bool -> < print_1 : 'a; print_2 : 'a; .. > -> 'a = <fun>
(* 関数呼び出し *)
# let a = f true;;
val a : < print_1 : '_a; print_2 : '_a; _.. > -> '_a = <fun>
(* print_2 を持たないので型エラー *)
# a new print_class1;;
Error: This expression has type print_class1
but an expression was expected of type print_class2
The first object type has no method print_2
(* オブジェクト型を満たすのでOK *)
# a new print_class2;;
1- : unit = ()
抽象メソッド・抽象クラス
後で継承することを前提とした定義
-
抽象メソッド => 定義が空のメソッド
method virtual メソッド : 型
-
抽象クラス => 抽象メソッドをもつクラス定義
class virtual
(* 抽象クラスの定義 *)
# class virtual abstruct_print =
object (self)
method virtual print : unit (* 抽象メソッド *)
end;;
class virtual abstruct_print : object method virtual print : unit end
(* 抽象クラスを継承 *)
# class print_hoge =
object (self)
inherit abstruct_print
method print = Printf.printf "hoge!\n"
end;;
class print_hoge : object method print : unit end
多相クラス(ジェネリクス)
オブジェクト・クラスを型に関してパラメータ化して定義する。
クラス定義 => クラスだけではなく同じ名前を持つオブジェクト型を定義すること
そのクラス型を多層的定義に定義するには、型変数の宣言が必要
- 明示的に型変数を宣言する
- 1で定義した型変数を定義内で参照する
class [型変数, ...] クラス名 object ... end
- 任意の型の値を詰められるスタックを定義する (公式サイトからの引用)
# class ['a] stack =
object (self)
val mutable list = ( [] : 'a list ) (* インスタンス変数 *)
method push x = list <- x :: list (* スタックへのpush *)
method pop = (* スタックからの取り出し(pop) *)
let result = List.hd list in
list <- List.tl list;
result
method peek = List.hd list (* スタックのピーク *)
method size = List.length list (* スタックのサイズ *)
end;;
class ['a] stack :
object
val mutable list : 'a list
method peek : 'a
method pop : 'a
method push : 'a -> unit
method size : int
end
- 多相クラスを使う
(* スタックのインスタンスを生成 *)
# let s = new stack;;
(* 型変数が未束縛の状態でインスタンスが生成される *)
val s : '_a stack = <obj>
(* float を突っ込む *)
# s#push 1.0;;
- : unit = ()
(* 型変数 '_a が float に束縛される *)
# s;;
- : float stack = <obj>
多相メソッド
多相クラスと同じくメソッドも多相化できる。
詳しくは http://ocaml.jp/refman/ch03s11.html
参考文献
非常に分かりやすく、OCamlを学ぶ際にオススメです。