val neg : < times : float -> 'a; .. > -> 'a = <fun>
#class account = objectvalmutable balance = zero method balance = balance method deposit x = balance <- balance # plus x method withdraw x = if x#leq balance then (balance <- balance # plus (neg x); x) else zero end;;
class account : objectvalmutable balance : Euro.c method balance : Euro.c method deposit : Euro.c -> unit method withdraw : Euro.c -> Euro.c end
#let c = new account in c # deposit (euro 100.); c # withdraw (euro 50.);;
#class safe_account = objectinherit account method deposit x = if zero#leq x then balance <- balance#plus x end;;
class safe_account : objectvalmutable balance : Euro.c method balance : Euro.c method deposit : Euro.c -> unit method withdraw : Euro.c -> Euro.c end
然而,可以透過以下定義更安全地修復錯誤
#class safe_account = objectinherit account as unsafe method deposit x = if zero#leq x then unsafe # deposit x else raise (Invalid_argument "deposit") end;;
class safe_account : objectvalmutable balance : Euro.c method balance : Euro.c method deposit : Euro.c -> unit method withdraw : Euro.c -> Euro.c end
特別是,這不需要了解 deposit 方法的實作。
為了追蹤操作,我們使用可變欄位 history 和私有方法 trace 來擴充類別,以在日誌中新增操作。然後重新定義要追蹤的每個方法。
#type 'a operation = Deposit of 'a | Retrieval of 'a;;
type 'a operation = Deposit of 'a | Retrieval of 'a
#class account_with_history = object (self) inherit safe_account as super valmutable history = [] methodprivate trace x = history <- x :: history method deposit x = self#trace (Deposit x); super#deposit x method withdraw x = self#trace (Retrieval x); super#withdraw x method history = List.rev history end;;
class account_with_history : objectvalmutable balance : Euro.c valmutable history : Euro.c operation list method balance : Euro.c method deposit : Euro.c -> unit method history : Euro.c operation list methodprivate trace : Euro.c operation -> unit method withdraw : Euro.c -> Euro.c end
可能希望開設帳戶並同時存入一些初始金額。雖然初始實作沒有解決此需求,但可以使用初始化器來達成。
#class account_with_deposit x = objectinherit account_with_history initializer balance <- x end;;
class account_with_deposit : Euro.c -> objectvalmutable balance : Euro.c valmutable history : Euro.c operation list method balance : Euro.c method deposit : Euro.c -> unit method history : Euro.c operation list methodprivate trace : Euro.c operation -> unit method withdraw : Euro.c -> Euro.c end
更好的替代方案是
#class account_with_deposit x = object (self) inherit account_with_history initializer self#deposit x end;;
class account_with_deposit : Euro.c -> objectvalmutable balance : Euro.c valmutable history : Euro.c operation list method balance : Euro.c method deposit : Euro.c -> unit method history : Euro.c operation list methodprivate trace : Euro.c operation -> unit method withdraw : Euro.c -> Euro.c end
確實,後者更安全,因為對 deposit 的呼叫將自動受益於安全檢查和追蹤。讓我們測試一下
#let ccp = new account_with_deposit (euro 100.) inlet _balance = ccp#withdraw (euro 50.) in ccp#history;;
- : Euro.c operation list = [Deposit <obj>; Retrieval <obj>]
可以使用以下多型函式來關閉帳戶
#let close c = c#withdraw c#balance;;
val close : < balance : 'a; withdraw : 'a -> 'b; .. > -> 'b = <fun>
當然,這適用於各種帳戶。
最後,我們將帳戶的幾個版本收集到一個模組 Account 中,該模組是從某種貨幣中抽象出來的。
#let today () = (01,01,2000) (* an approximation *)module Account (M:MONEY) = structtype m = M.c let m = new M.c let zero = m 0. class bank = object (self) valmutable balance = zero method balance = balance valmutable history = [] methodprivate trace x = history <- x::history method deposit x = self#trace (Deposit x); if zero#leq x then balance <- balance # plus x else raise (Invalid_argument "deposit") method withdraw x = if x#leq balance then (balance <- balance # plus (neg x); self#trace (Retrieval x); x) else zero method history = List.rev history endclasstype client_view = objectmethod deposit : m -> unit method history : m operation list method withdraw : m -> m method balance : m endclassvirtual check_client x = let y = if (m 100.)#leq x then x else raise (Failure "Insufficient initial deposit") inobject (self) initializer self#deposit y methodvirtual deposit: m -> unit endmodule Client (B : sigclass bank : client_view end) = structclass account x : client_view = objectinherit B.bank inherit check_client x endlet discount x = let c = new account x inif today() < (1998,10,30) then c # deposit (m 100.); c endend;;
將客戶視圖作為函式子 Client 提供是很重要的,這樣在 bank 可能專門化之後,仍然可以建立客戶帳戶。函式子 Client 可以保持不變,並將新的定義傳遞給它,以初始化擴展帳戶的客戶視圖。
#module Investment_account (M : MONEY) = structtype m = M.c module A = Account(M) class bank = objectinherit A.bank as super method deposit x = if (new M.c 1000.)#leq x then print_string "Would you like to invest?"; super#deposit x endmodule Client = A.Client end;;
當帳戶的某些新功能可以提供給客戶時,也可以重新定義函式子 Client。
#module Internet_account (M : MONEY) = structtype m = M.c module A = Account(M) class bank = objectinherit A.bank method mail s = print_string s endclasstype client_view = objectmethod deposit : m -> unit method history : m operation list method withdraw : m -> m method balance : m method mail : string -> unit endmodule Client (B : sigclass bank : client_view end) = structclass account x : client_view = objectinherit B.bank inherit A.check_client x endendend;;
#class better_string s = objectval repr = s method get n = String.get repr n method print = print_string repr method escaped = {< repr = String.escaped repr >} method sub start len = {< repr = String.sub s start len >} end;;
class better_string : string -> object ('a) val repr : string method escaped : 'a method get : int -> char method print : unit method sub : int -> int -> 'a end
如推斷的類型所示,方法 escaped 和 sub 現在會傳回與類別類型相同的物件。
另一個困難之處是方法 concat 的實作。為了將字串與同類別的另一個字串連接起來,必須能夠從外部存取實例變數。因此,必須定義一個傳回 s 的方法 repr。以下是字串的正確定義
#class ostring s = object (self : 'mytype) val repr = s method repr = repr method get n = String.get repr n method print = print_string repr method escaped = {< repr = String.escaped repr >} method sub start len = {< repr = String.sub s start len >} method concat (t : 'mytype) = {< repr = repr ^ t#repr >} end;;
class ostring : string -> object ('a) val repr : string method concat : 'a -> 'a method escaped : 'a method get : int -> char method print : unit method repr : string method sub : int -> int -> 'a end
#class ['a] stack = objectvalmutable l = ([] : 'a list) method push x = l <- x::l method pop = match l with [] -> raise Empty | a::l' -> l <- l'; a method clear = l <- [] method length = List.length l end;;
class ['a] stack : objectvalmutable l : 'a list method clear : unit method length : int method pop : 'a method push : 'a -> unit end
然而,編寫一個迭代堆疊的方法會比較棘手。一個方法 fold 的類型將會是 ('b -> 'a -> 'b) -> 'b -> 'b。這裡 'a 是堆疊的參數。參數 'b 與 'a stack 類別無關,而是與傳遞給方法 fold 的參數有關。一個簡單的方法是將 'b 作為 stack 類別的額外參數
#class ['a, 'b] stack2 = objectinherit ['a] stack method fold f (x : 'b) = List.fold_left f x l end;;
class ['a, 'b] stack2 : objectvalmutable l : 'a list method clear : unit method fold : ('b -> 'a -> 'b) -> 'b -> 'b method length : int method pop : 'a method push : 'a -> unit end
#class ['a] stack3 = objectinherit ['a] stack method fold : 'b. ('b -> 'a -> 'b) -> 'b -> 'b = fun f x -> List.fold_left f x l end;;
class ['a] stack3 : objectvalmutable l : 'a list method clear : unit method fold : ('b -> 'a -> 'b) -> 'b -> 'b method length : int method pop : 'a method push : 'a -> unit end
這是第 3.17 節中看到的友元函式的另一個實例 3.17。事實上,這是在沒有物件的情況下,在模組 Set 中使用的相同機制。
在物件導向版本的集合中,我們只需要新增一個額外的方法 tag 來傳回集合的表示。由於集合在元素類型中是參數化的,因此方法 tag 具有參數類型 'a tag,在模組定義中具體化,但在其簽名中是抽象的。從外部來看,它將確保具有相同類型的方法 tag 的兩個物件將共享相同的表示。
#moduletype SET = sigtype 'a tag class ['a] c : object ('b) method is_empty : bool method mem : 'a -> bool method add : 'a -> 'b method union : 'b -> 'b method iter : ('a -> unit) -> unit method tag : 'a tag endend;;
#module Set : SET = structletrec merge l1 l2 = match l1 with [] -> l2 | h1 :: t1 -> match l2 with [] -> l1 | h2 :: t2 -> if h1 < h2 then h1 :: merge t1 l2 elseif h1 > h2 then h2 :: merge l1 t2 else merge t1 l2 type 'a tag = 'a list class ['a] c = object (_ : 'b) val repr = ([] : 'a list) method is_empty = (repr = []) method mem x = List.exists (( = ) x) repr method add x = {< repr = merge [x] repr >} method union (s : 'b) = {< repr = merge repr s#tag >} method iter (f : 'a -> unit) = List.iter f repr method tag = repr endend;;
class ['a, 'event] subject : object ('b) constraint 'a = < notify : 'b -> 'event -> unit; .. > valmutable observers : 'a list method add_observer : 'a -> unit method notify_observers : 'event -> unit end
#class ['observer] window_subject = let id = count := succ !count; !count inobject (self) inherit ['observer, event] subject valmutable position = 0 method identity = id method move x = position <- position + x; self#notify_observers Move method draw = Printf.printf "{Position = %d}\n" position; end;;
class ['a] window_subject : object ('b) constraint 'a = < notify : 'b -> event -> unit; .. > valmutable observers : 'a list valmutable position : int method add_observer : 'a -> unit method draw : unit method identity : int method move : int -> unit method notify_observers : event -> unit end
#class ['subject] window_observer = objectinherit ['subject, event] observer method notify s e = s#draw end;;
class ['a] window_observer : objectconstraint 'a = < draw : unit; .. > method notify : 'a -> event -> unit end
正如預期的,window 的型別是遞迴的。
#let window = new window_subject;;
val window : (< notify : 'a -> event -> unit; .. > as '_weak3) window_subject as 'a = <obj>
然而,window_subject 和 window_observer 這兩個類別並非相互遞迴。
#let window_observer = new window_observer;;
val window_observer : (< draw : unit; .. > as '_weak4) window_observer = <obj>
class ['a] richer_window_subject : object ('b) constraint 'a = < notify : 'b -> event -> unit; .. > valmutable observers : 'a list valmutable position : int valmutable size : int valmutable top : bool method add_observer : 'a -> unit method draw : unit method identity : int method move : int -> unit method notify_observers : event -> unit method raise : unit method resize : int -> unit end
#class ['subject] richer_window_observer = objectinherit ['subject] window_observer as super method notify s e = if e <> Raise then s#raise; super#notify s e end;;
class ['a] richer_window_observer : objectconstraint 'a = < draw : unit; raise : unit; .. > method notify : 'a -> event -> unit end