前回は多重継承と Mix-in について説明しました。今回は「クラス型」について説明します。
通常、オブジェクトの型は型推論により定義されますが、class 文でクラスを定義するとき、シグネチャのようにクラスのデータ型を明示的に宣言することができます。これを「クラス型」といいます。クラス型を宣言することでインスタンス変数やプライベートメソッドを隠蔽することができます。ただし、公開メソッドや抽象メソッドは隠蔽することができません。
クラス型は次のように指定します。
class クラス名 : object inherit スーパークラス名 ... val インスタンス変数名1 : 型v1 val インスタンス変数名2 : 型v2 ... method メソッド名1 : 型m1 method メソッド名2 : 型m2 ... end = object ... end
モジュールと同様に、クラス名の後ろに : を付けて object ... end の中にインスタンス変数やメソッドの型を定義します。この中で継承 (inherit) を使ってもかまいません。
簡単な例を示しましょう。前回作成したクラス point でクラス型を宣言します。次のリストを見てください。
リスト 1 : point クラスの定義 (* ポイント *) class point xi yi : object ('self_type) method x : float method y : float method move : float -> float -> unit method equal : 'self_type -> bool end = object (self: 'self_type) val mutable x = xi val mutable y = yi method x = x method y = y method move dx dy = x <- x +. dx; y <- y +. dy method equal (p: 'self_type) = x = p#x && y = p#y end (* 色付きのポイント *) class colored_point xi yi (ci: int) : object ('self_type) inherit point method color : int method equal : 'self_type -> bool end = object (self: 'self_type) inherit point xi yi as super val mutable c = ci method color = c method equal (p: 'self_type) = super#equal p && c = p#color end
クラス型を宣言するとき、object ('self_type) のように自分自身の型を指定することができます。これで equal のようなバイナリメソッドの型を定義することができます。クラス point と colored_point の型宣言で、インスタンス変数の型は定義されていません。これでインスタンス変数の情報を隠蔽することができます。
実際にクラスを定義すると、次のように表示されます。
class point : float -> float -> object ('a) method equal : 'a -> bool method move : float -> float -> unit method x : float method y : float end class colored_point : float -> float -> int -> object ('a) method color : int method equal : 'a -> bool method move : float -> float -> unit method x : float method y : float end
このように、クラス型を宣言することでインスタンス変数を隠蔽することができます。
ところで、クラス型はシグネチャのように名前を付けることができます。これをクラスのインターフェースと呼ぶことがあります。クラス型の名前は class type 宣言で定義します。
class type 名前 = object inherit スーパークラス名 ... val インスタンス変数名1 : 型v1 val インスタンス変数名2 : 型v2 ... method メソッド名1 : 型m1 method メソッド名2 : 型m2 ... end
クラス型の名前はクラス名と同様にデータ型の指定や制限に使うことができます。
簡単な例を示します。point クラスの型に名前をつけてみましょう。
リスト 2 : point クラス (2) (* point クラスの型名 *) class type point_type = object ('self_type) method x : float method y : float method move : float -> float -> unit method equal : 'self_type -> bool end (* point クラス *) class point xi yi : point_type = object (self: 'self_type) val mutable x = xi val mutable y = yi method x = x method y = y method move dx dy = x <- x +. dx; y <- y +. dy method equal (p: 'self_type) = x = p#x && y = p#y end (* colored_point の型名 *) class type colored_point_type = object ('self_type) inherit point method color : int method equal : 'self_type -> bool end (* colored_point クラス *) class colored_point xi yi (ci: int) : colored_point_type = object (self: 'self_type) inherit point xi yi as super val mutable c = ci method color = c method equal (p: 'self_type) = super#equal p && c = p#color end
point クラスの型名を point_type とし、colored_point クラスの型名を colored_point_type としました。これを使って、クラスの型を指定することができます。実際にクラスを定義すると次のように表示されます。
class type point_type = object ('a) method equal : 'a -> bool method move : float -> float -> unit method x : float method y : float end class point : float -> float -> point_type class type colored_point_type = object ('a) method color : int method equal : 'a -> bool method move : float -> float -> unit method x : float method y : float end class colored_point : float -> float -> int -> colored_point_type
次は 2 点間の距離を計算する関数 distance を作ります。次のリストを見てください。
リスト 3 : 2 点間の距離を求める let distance p1 p2 = let dx = p1#x -. p2#x and dy = p1#y -. p2#y in sqrt (dx *. dx +. dy *. dy)
プログラムは簡単です。引数 p1 と p2 は点を表すオブジェクトです。メソッド x, y で座標を求め、2 点間の距離を計算するだけです。distance をコンパイルすると、データ型は次のように推論されます。
val distance : < x : float; y : float; .. > -> < x : float; y : float; .. > -> float
引数のデータ型はメソッド x, y を持つ任意のオブジェクトになります。これで、point と colored_point の距離を求めることができます。ところが、それ以外のオブジェクトでも、メソッド x, y が定義されていれば distance を呼び出すことができます。たとえば、3 次元の座標を表す point3d クラスを定義しましょう。
リスト 4 : point3d クラス class point3d xi yi zi = object (self: 'self_type) val mutable x = xi val mutable y = yi val mutable z = zi method x = x method y = y method z = z method move dx dy dz = x <- x +. dx; y <- y +. dy; z <- z +. dz method equal (p: 'self_type) = x = p#x && y = p#y && z = p#z end
point3d はメソッド x, y を持っていて、そのデータ型が point と同じなので、point3d のオブジェクトを distance の引数に渡してもコンパイルエラーにはなりません。そこで、次のように引数のデータ型を制限します。
リスト 5 : 2 点間の距離を求める (2) let distance (p1: point_type) (p2: point_type) = let dx = p1#x -. p2#x and dy = p1#y -. p2#y in sqrt (dx *. dx +. dy *. dy)
distance の引数のデータ型は point_type に制限されます。これで point3d のオブジェクトを渡すとエラーになりますが、これでは colored_point のオブジェクトもエラーになってしまいます。このような場合、point と colored_point の両方を満たす部分型を指定できると便利です。
OCaml の場合、クラス名またはクラス型名の前に # を付けると、そのデータ型の任意の部分型を指定することができます。たとえば、メソッド x, y, move を持つクラス型を定義します。
リスト 6 : クラス型 point0 の定義 class type point0 = object method move : float -> float -> unit method x : float method y : float end
point と colored_point の共通のメソッドを point0 で定義します。すると、#point0 は point0 の任意の部分型を表すことになります。実際にオブジェクトの型を示すと、次のようになります。
point0 : < move float -> float -> unit; x : float; y : float > #point0 : < move float -> float -> unit; x : float; y : float; .. >
#point0 は poit0 の任意の部分型なので、最後にピリオドが 2 つ ( .. ) が付いています。この #point0 を使って distance の引数の型を制限することができます。
リスト 7 : 2 点間の距離を求める (2) let distance (p1: (#point0 as 'a)) (p2: 'a) = let dx = p1#x -. p2#x and dy = p1#y -. p2#y in sqrt (dx *. dx +. dy *. dy)
val distance : (#point0 as 'a) -> 'a -> float
p1 と p2 は同じデータ型であることを示すため、p1 の型指定で #point0 as 'a により p1 に型変数 'a を付けて、第 2 引数 p2 のデータ型は 'a で指定します。p2 の型指定を #point0 とすると、p1 とデータ型が異なるオブジェクト、たとえば、引数のデータ型が point と colored_point でもコンパイルエラーにはなりません。
point と colored_point は point0 の部分型なので、distance で距離を求めることができます。point3d はメソッド move の型が point0 の move と異なるので、point0 の部分型にはなりません。したがって、point3d のオブジェクトを distance に渡すとコンパイルエラーになります。
簡単な実行例を示しましょう。
# let p1 = new point 0. 0. ;; val p1 : point = <obj> # let p2 = new point 10. 10. ;; val p2 : point = <obj> # let p3 = new colored_point 0. 0. 1;; val p3 : colored_point = <obj> # let p4 = new colored_point 5. 5. 1;; val p4 : colored_point = <obj> # distance p1 p2;; - : float = 14.142135623730951 # distance p3 p4;; - : float = 7.0710678118654755
distance p1 p3 とするとエラーになります。
インスタンス変数は個々のインスタンス (オブジェクト) に格納される変数です。その値はインスタンスによって変わります。クラスで共通の変数や定数を使いたい場合は、class の中で変数や定数を定義します。
class クラス名 = let ... 局所変数や局所関数の定義 ... in object ... end
OCaml は class 文の = と object の間に、let ... in で局所変数や局所関数を定義することができます。一般に、クラス共通で使用する変数や定数のことを「クラス変数」や「クラス定数」といいます。OCaml の場合、クラス内で定義された局所変数は、クラス変数やクラス定数として利用することができます。
簡単な例を示しましょう。
リスト 8 : クラス定数とクラス変数 class foo = let name = "foo" in let value = ref 0 in object method show = Printf.printf "%s %d\n" name !value method update x = value := x end
let で定義された局所変数は同じクラスのメソッドからアクセスすることができます。リスト 8 では局所変数 name がクラス定数になり、value がクラス変数になります。クラス変数は値を書き換えることがあるので、value は参照型変数として定義しています。メソッド show は name と value を表示します。メソッド update は value の値を書き換えます。
それでは実際に試してみましょう。
# let a = new foo;; val a : foo = <obj> # let b = new foo;; val b : foo = <obj> # a#show;; foo 0 - : unit = () # b#show;; foo 0 - : unit = () # a#update 10;; - : unit = () # a#show;; foo 10 - : unit = () # b#show;; foo 10 - : unit = ()
2 つのインスタンスを生成して変数 a, b にセットします。どちらのインスタンスもメソッド show で name と value の値を表示すると foo 0 になります。ここで、a#update で value の値を 10 に書き換えます。a#show は foo 10 を表示しますが、b#show も foo 10 を表示します。局所変数 value は 2 つのインスタンスで共有されていることがわかります。
また、foo を継承して新しいクラスを作ると、そのクラスも foo の局所変数を共有します。簡単な例を示しましょう。
# class bar = object inherit foo end;; class bar : object method show : unit method update : int -> unit end # let a = new foo;; val a : foo = <obj> # let b = new bar;; val b : bar = <obj> # a#show;; foo 0 - : unit = () # b#show;; foo 0 - : unit = () # a#update 10;; - : unit = () # a#show;; foo 10 - : unit = () # b#show;; foo 10 - : unit = ()
このように、foo と bar のインスタンスは局所変数 name と value の値を共有しています。ただし、クラス bar のメソッドから foo の局所変数に直接アクセスすることはできません。foo のメソッドを経由してアクセスする必要があります。ご注意くださいませ。
前回作成した可変長配列クラス arraylist を継承して、要素を昇順に並べて格納するクラス sorted_arraylist を作成してください。
リスト : sorted_arraylist クラス (* 例外 *) exception Can_not_use class ['a] sorted_arraylist compare init_size (init_value : 'a) = object(self) inherit ['a] arraylist init_size init_value as super method private move i = if i = 0 then () else let a = self#get (i - 1) in let b = self#get i in if compare a b <= 0 then () else (super#set i a; super#set (i - 1) b; self#move (i - 1)) method push x = super#push x; self#move (self#length - 1) method set i x = raise Can_not_use end
inherit で arraylist を継承するときに別名 super を付けます。メソッド push はスーパークラスのメソッド super#push を呼び出して x を末尾に追加し、プライベートメソッド move で末尾データを適切な位置に挿入します。move は i - 1 番目のデータ a と i 番目のデータ b を比較して、a > b であれば a と b を交換して、次のデータと比較します。i が 0 または a <= b であれば処理を終了します。
メソッド set は例外 Can_not_use を送出するだけです。move でデータを交換するときは super#set で上位クラスのメソッド set を呼び出していることに注意してください。
なお、メソッド move はデータの挿入位置を求めるのに線形探索しているので、データ数が多くなると時間がかかるようになります。データ数を N とすると、実行時間は N に比例します。ここで「二分探索 (binary search)」を使うと、log2N に比例する時間でデータの挿入位置を求めることができます。興味のある方はプログラムを改造してみてください。
簡単な実行例を示します。
class ['a] sorted_arraylist : ('a -> 'a -> int) -> int -> 'a -> object val mutable buff : 'a array val mutable idx : int val mutable top : int method begin0 : unit method count_if : ('a -> bool) -> int method filter : ('a -> bool) -> 'a list method find_if : ('a -> bool) -> 'a option method fold_left : ('b -> 'a -> 'b) -> 'b -> 'b method get : int -> 'a method iter : ('a -> unit) -> unit method length : int method map : ('a -> 'b) -> 'b list method private move : int -> unit method next : 'a option method peek : 'a method pop : 'a method position_if : ('a -> bool) -> int method push : 'a -> unit method set : int -> 'a -> unit end
# let a = new sorted_arraylist compare 5 0;; val a : int sorted_arraylist =# List.iter (fun x -> a#push x) [5; 4; 6; 3; 7; 2; 8; 1; 9; 0];; - : unit = () # a#iter (fun x -> print_int x; print_newline());; 0 1 2 3 4 5 6 7 8 9 - : unit = () # a#length;; - : int = 10 # a#peek;; - : int = 9 # a#pop;; - : int = 9 # a#length;; - : int = 9 # a#get 0;; - : int = 0 # a#set 0 100;; Exception: Can_not_use.
今回は集合を例題にして、モジュール (ファンクタ) とオブジェクト指向を使ったプログラムを作りましょう。
クラスはモジュール (ファンクタ) の中でも定義することもできます。たとえば、ファンクタ MakeSet の中でクラス set を定義すると、次のようになります。
リスト 9 : ファンクタ内でのクラス定義 (* ストラクチャ *) module type ITEMTYPE = sig type t val compare : t -> t -> int end (* ファンクタ *) module MakeSet(Item: ITEMTYPE) : sig type t = Item.t class set : object ('a) method copy : 'a method delete : t -> unit method difference : 'a -> 'a method insert : t -> unit method intersection : 'a -> 'a method is_equal : 'a -> bool method is_subset : 'a -> bool method iter : (t -> unit) -> unit method length : int method member : t -> bool method union : 'a -> 'a end end = struct type t = Item.t class set = object(self : 'self_type) val mutable content = ([]: t list) val mutable size = 0 method member p = let rec mem_eq = function [] -> false | x::xs -> if Item.compare p x = 0 then true else mem_eq xs in mem_eq content method insert p = if not (self#member p) then begin size <- size + 1; content <- p::content end else () method delete p = if self#member p then (content <- List.filter (fun x -> Item.compare p x <> 0) content; size <- size - 1) else raise Not_found ... 省略 ... end end
シグネチャでは "class クラス名 : object ... end" でクラス型を定義します。シグネチャでデータ型 t を宣言しているので、t を使ってクラス型を定義します。ファンクタ本体ではデータ型 t を Item.t に定義し、それを使ってクラス set を定義します。要素の比較はストラクチャ Item の関数 compare を使います。あとは今までのプログラムと同じです。
簡単な例として、整数値 (int) を格納する集合 IntSet を作ってみましょう。IntSet を定義すると次のように表示されます。
# module IntSet = MakeSet(struct type t = int let compare x y = x - y end) module IntSet : sig type t = int class set : object ('a) method copy : 'a method delete : t -> unit method difference : 'a -> 'a method insert : t -> unit method intersection : 'a -> 'a method is_equal : 'a -> bool method is_subset : 'a -> bool method iter : (t -> unit) -> unit method length : int method member : t -> bool method union : 'a -> 'a end end
それでは実際に試してみましょう。
# open IntSet;; # let print s = s#iter (fun x -> print_int x#get; print_string " ");; val print : < iter : (int -> unit) -> 'a; .. > -> 'a = <fun> # let a = new set compare;; val a : IntSet.set = <obj> # for i = 1 to 5 do a#insert i done;; - : unit = () # print a;; 5 4 3 2 1 - : unit = () # let b = new set;; val b : IntSet.set = <obj> # for i = 4 to 8 do b#insert i done;; - : unit = () # print b;; 8 7 6 5 4 - : unit = () # print (a#union b);; 1 2 3 8 7 6 5 4 - : unit = () # print (a#intersection b);; 4 5 - : unit = () # print (a#difference b);; 1 2 3 - : unit = ()
このようにファンクタとクラスを組み合わせることも簡単にできます。
ところで、拙作のページ 集合 で作成したクラス set は、要素を比較する関数をクラスの引数に渡しました。ここでは異なる方法を試してみましょう。集合に格納する要素をオブジェクトとし、そのオブジェクトに要素同士を比較するメソッド compare を定義することにします。
それではプログラムを作りましょう。最初にクラスを定義します。
リスト 10 : クラス set の定義 class ['a] set = object(self: 'self_type) val mutable content = ([]: 'a list) val mutable size = 0 method member p = let rec mem_eq = function [] -> false | x::xs -> if p#compare x = 0 then true else mem_eq xs in mem_eq content method insert p = if not (self#member p) then begin size <- size + 1; content <- p::content end else () method delete p = if self#member p then (content <- List.filter (fun x -> p#compare x <> 0) content; size <- size - 1) else raise Not_found ... 省略 ... end
今回はオブジェクトのメソッド compare を使ってプログラムを作るので、型推論により型変数 'a はあるオブジェクトの型に制限されます。これはあとで説明します。要素の比較はオブジェクトのメソッド compare を使います。compare の仕様は OCaml の関数 compare と同じです。プログラムは compare p x を p#compare x と書き換えるだけです。
実際に set を定義すると次のように表示されます。
class ['a] set : object ('b) constraint 'a = < compare : 'a -> int; .. > val mutable content : 'a list val mutable size : int method copy : 'b method delete : 'a -> unit method difference : 'b -> 'b method insert : 'a -> unit method intersection : 'b -> 'b method is_equal : 'b -> bool method is_subset : 'b -> bool method iter : ('a -> unit) -> unit method length : int method member : 'a -> bool method union : 'b -> 'b end
constraint は「制約」という意味で、集合の要素 'a はメソッド compare を持つオブジェクトでなければならないことを表しています。今回は OCaml の型推論にお任せしましたが、プログラムで constraint を記述することもできます。
それでは簡単な実行例を示しましょう。まず最初に、集合に格納するオブジェクトを定義します。
リスト 11 : 要素となるオブジェクト class ['a] item (x: 'a) = object(self: 'self_type) val n = x method get = n method compare (x: 'self_type) = compare n x#get end
データの比較は関数 compare で行います。オブジェクト以外のデータ型であれば、これで大小関係を比較することができます。
実行例は次のようになります。
# let print s = s#iter (fun x -> print_int x#get; print_string " ");; val print : < iter : (< get : int; .. > -> unit) -> 'a; .. > -> 'a = <fun> # let a = new set;; val a : (< compare : 'a -> int; _.. > as 'a) set = <obj> # for i = 1 to 5 do a#insert (new item i) done;; - : unit = () # print a;; 5 4 3 2 1 - : unit = () # let b = new set;; val b : (< compare : 'a -> int; _.. > as 'a) set = <obj> # for i = 4 to 8 do b#insert (new item i) done;; - : unit = () # print b;; 8 7 6 5 4 - : unit = () # print (a#union b);; 1 2 3 8 7 6 5 4 - : unit = () # print (a#intersection b);; 4 5 - : unit = () # print (a#difference b);; 1 2 3 - : unit = ()
最初に集合の要素を表示する関数 print を定義します。型推論により、引数 s はメソッド iter を持つオブジェクトで、iter に渡す関数の引数はメソッド get を持つオブジェクトであることが示されています。
次に、集合 a と b を生成します。a は {1, 2, 3, 4. 5} で、b は {4, 5, 6, 7, 8} です。集合演算を行うと、a と b の和は {1, 2, 3, 4, 5, 6, 7, 8} になり、積は {4, 5} になり、差は {1, 2, 3} になります。
このように、格納するオブジェクトのメソッド compare を使って集合を表すクラスを定義することもできます。
ところで、ファンクタで定義した集合でも、オブジェクトを格納することができます。次の例を見てください。
# module IntObjSet = MakeSet(struct type t = int item let compare x y = x#compare y end);; module IntObjSet : sig type set val create : unit -> set val member : int item -> set -> bool val insert : int item -> set -> set val delete : int item -> set -> set val union : set -> set -> set val intersection : set -> set -> set val difference : set -> set -> set val is_subset : set -> set -> bool val is_equal : set -> set -> bool val iter : (int item -> unit) -> set -> unit val set_of_list : int item list -> set val list_of_set : set -> int item list end # open IntObjSet;; # let print s = iter (fun x -> print_int x#get; print_string " ") s;; val print : IntObjSet.set -> unit = <fun> # let a = set_of_list (List.map (fun x -> new item x) [1;2;3;4;5]);; val a = IntObjSet.set = <abstr> # print a;; 1 2 3 4 5 - : unit = () # let b = set_of_list (List.map (fun x -> new item x) [4;5;6;7;8]);; val b = IntObjSet.set = <abstr> # print b;; 4 5 6 7 8 - : unit = () # print (union a b);; 1 2 3 4 5 6 7 8 - : unit = () # print (intersection a b);; 4 5 - : unit = () # print (difference a b);; 1 2 3 - : unit = ()
ファンクタに渡すストラクチャで、type t = int item とし、compare x y = x#compare x とすれば、オブジェクト item を格納するモジュール IntObjSet を生成することができます。このように、OCaml にはモジュール (ファンクタ) という優れた機能があるので、複数の要素を格納するデータ構造 (コレクション) をクラスで実装することは少ないのかもしれませんね。
可変長配列を操作するモジュール Arraylist をクラスを使わないで定義してください。
リスト : モジュール Arraylist module Arraylist : sig type 'a arraylist val create : int -> 'a -> 'a arraylist val push : 'a arraylist -> 'a -> unit val pop : 'a arraylist -> 'a val peek : 'a arraylist -> 'a val get : 'a arraylist -> int -> 'a val set : 'a arraylist -> int -> 'a -> unit val length : 'a arraylist -> int val find_if : ('a -> bool) -> 'a arraylist -> 'a option val position_if : ('a -> bool) -> 'a arraylist -> int val count_if : ('a -> bool) -> 'a arraylist -> int val map : ('a -> 'b) -> 'a arraylist -> 'b arraylist val filter : ('a -> bool) -> 'a arraylist -> 'a arraylist val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b arraylist -> 'a val iter : ('a -> 'b) -> 'a arraylist -> unit end = struct type 'a arraylist = {mutable buff : 'a array; mutable top : int} let create init_size init_value = {buff = Array.make init_size init_value; top = 0} let push xs x = if xs.top >= Array.length xs.buff then let newbuff = Array.make (2 * Array.length xs.buff) xs.buff.(0) in Array.blit xs.buff 0 newbuff 0 (Array.length xs.buff); xs.buff <- newbuff else (); xs.buff.(xs.top) <- x; xs.top <- xs.top + 1 let pop xs = if xs.top = 0 then raise Empty else (xs.top <- xs.top - 1; xs.buff.(xs.top)) let peek xs = if xs.top = 0 then raise Empty else xs.buff.(xs.top - 1) let get xs i = if i < 0 || i >= xs.top then raise Out_of_range else xs.buff.(i) let set xs i x = if i < 0 || i >= xs.top then raise Out_of_range else xs.buff.(i) <- x let length xs = xs.top (* 高階関数 *) let find_if pred xs = let rec _find i = if i >= xs.top then None else if pred xs.buff.(i) then Some xs.buff.(i) else _find (i + 1) in _find 0 let position_if pred xs = let rec _position i = if i >= xs.top then -1 else if pred xs.buff.(i) then i else _position (i + 1) in _position 0 let count_if pred xs = let rec _count i c = if i >= xs.top then c else _count (i + 1) (if pred xs.buff.(i) then c + 1 else c) in _count 0 0 let map f xs = let ys = create xs.top (f xs.buff.(0)) in ys.top <- 1; for i = 1 to (xs.top - 1) do push ys (f xs.buff.(i)) done; ys let filter pred xs = let ys = create xs.top xs.buff.(0) in for i = 0 to (xs.top - 1) do if pred xs.buff.(i) then push ys xs.buff.(i) else () done; ys let fold_left f a xs = let rec _fold i a = if i >= xs.top then a else _fold (i + 1) (f a xs.buff.(i)) in _fold 0 a let iter f xs = let rec _iter i = if i >= xs.top then () else (f xs.buff.(i); _iter (i + 1)) in _iter 0 end
# open Arraylist;; # let a = create 4 0;; val a : int Arraylist.arraylist = <abstr> # for i = 1 to 8 do push a i done;; - : unit = () # length a;; - : int = 8 # for i = 0 to 7 do print_int (get a i); print_newline() done;; 1 2 3 4 5 6 7 8 - : unit = () # for i = 0 to 7 do set a i ((get a i) * 10) done;; - : unit = () # iter (fun x -> print_int x; print_newline()) a;; 10 20 30 40 50 60 70 80 - : unit = () # iter (fun x -> print_int x; print_newline()) b;; 100 400 900 1600 2500 3600 4900 6400 # let c = filter (fun x -> x mod 20 = 0) a;; val c : int Arraylist.arraylist = <abstr> # iter (fun x -> print_int x; print_newline()) c;; 20 40 60 80 - : unit = () # fold_left (+) 0 a;; - : int = 360 # fold_left (+) 0 b;; - : int = 20400 # find_if (fun x -> x > 50) a;; - : int option = Some 60 # find_if (fun x -> x > 100) a;; - : int option = None # position_if (fun x -> x > 50) a;; - : int = 5 # position_if (fun x -> x > 100) a;; - : int = -1 # count_if (fun x -> x > 50) a;; - : int = 3 # count_if (fun x -> x < 50) a;; - : int = 4 # count_if (fun x -> x < 0) a;; - : int = 0 # for i = 0 to (length a - 1) do print_int (pop a); print_newline() done;; 80 70 60 50 40 30 20 10 - : unit = () # length a;; - : int = 0