LoginSignup
6
3

More than 5 years have passed since last update.

OCaml 入門その5 オブジェクト指向機能

Last updated at Posted at 2017-12-25

ML Advent Calendar 2017

その4

オブジェクト指向機能

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; .. >の別名(?)

多層的オブジェクト型に与えられた引数の型の変化について

  1. 多層的な型を引数に与えた場合
(* 多層的関数を定義 *)
# 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>
  1. 多層的ではない型を引数に与えた場合
(* 異なる型の関数にすると *)
# 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
  1. 多層的なオブジェクト型を与えた場合
(* 多層的オブジェクト型の関数を定義する *)
# 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. 明示的に型変数を宣言する
  2. 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

参考文献

プログラミング in OCaml

非常に分かりやすく、OCamlを学ぶ際にオススメです。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3