summaryrefslogtreecommitdiff
path: root/manual/src/tutorials/advexamples.etex
blob: 1830ee2d7bc89ed1a149fc2a64252ed299f2c818 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
\chapter{Advanced examples with classes and modules}
%HEVEA\cutname{advexamples.html}
\label{c:advexamples}

{\it (Chapter written by Didier Rémy)}

\bigskip

\noindent

In this chapter, we show some larger examples using objects, classes
and modules.  We review many of the object features simultaneously on
the example of a bank account.  We show how modules taken from the
standard library can be expressed as classes.  Lastly, we describe a
programming pattern known as {\em virtual types} through the example
of window managers.

\section{s:extended-bank-accounts}{Extended example: bank accounts}

In this section, we illustrate most aspects of Object and inheritance
by refining, debugging, and specializing the following
initial naive definition of a simple bank account.  (We reuse the
module "Euro" defined at the end of chapter~\ref{c:objectexamples}.)
\begin{caml_eval}
module type MONEY =
  sig
    type t
    class c : float ->
      object ('a)
        val repr : t
        method value : t
        method print : unit
        method times : float -> 'a
        method leq : 'a -> bool
        method plus : 'a -> 'a
      end
  end;;
module Euro : MONEY =
  struct
    type t = float
    class c x =
      object (self : 'a)
        val repr = x
        method value = repr
        method print = print_float repr
        method times k = {< repr = k *. x >}
        method leq (p : 'a) = repr <= p#value
        method plus (p : 'a) = {< repr = x +. p#value >}
      end
  end;;
\end{caml_eval}
\begin{caml_example}{toplevel}
let euro = new Euro.c;;
let zero = euro 0.;;
let neg x = x#times (-1.);;
class account =
  object
    val mutable 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;;
let c = new account in c # deposit (euro 100.); c # withdraw (euro 50.);;
\end{caml_example}
We now refine this definition with a method to compute interest.
\begin{caml_example}{toplevel}
class account_with_interests =
  object (self)
    inherit account
    method private interest = self # deposit (self # balance # times 0.03)
  end;;
\end{caml_example}
We make the method "interest" private, since clearly it should not be
called freely from the outside. Here, it is only made accessible to subclasses
that will manage monthly or yearly updates of the account.

We should soon fix a bug in the current definition: the deposit method can
be used for withdrawing money by depositing negative amounts. We can
fix this directly:
\begin{caml_example}{toplevel}
class safe_account =
  object
    inherit account
    method deposit x = if zero#leq x then balance <- balance#plus x
  end;;
\end{caml_example}
However, the bug might be fixed more safely by  the following definition:
\begin{caml_example}{toplevel}
class safe_account =
  object
    inherit account as unsafe
    method deposit x =
      if zero#leq x then unsafe # deposit x
      else raise (Invalid_argument "deposit")
  end;;
\end{caml_example}
In particular, this does not require the knowledge of the implementation of
the method "deposit".

To keep track of operations, we extend the class with a mutable field
"history" and a private method "trace" to add an operation in the
log. Then each method to be traced is redefined.
\begin{caml_example}{toplevel}
type 'a operation = Deposit of 'a | Retrieval of 'a;;
class account_with_history =
  object (self)
    inherit safe_account as super
    val mutable history = []
    method private 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;;
\end{caml_example}
%% \label{ss:bank:initializer}
One may wish to open an account and simultaneously deposit some initial
amount. Although the initial implementation did not address this
requirement, it can be achieved by using an initializer.
\begin{caml_example}{toplevel}
class account_with_deposit x =
  object
    inherit account_with_history
    initializer balance <- x
  end;;
\end{caml_example}
A better alternative is:
\begin{caml_example}{toplevel}
class account_with_deposit x =
  object (self)
    inherit account_with_history
    initializer self#deposit x
  end;;
\end{caml_example}
Indeed, the latter is safer since the call to "deposit" will automatically
benefit from safety checks and from the trace.
Let's test it:
\begin{caml_example}{toplevel}
let ccp = new account_with_deposit (euro 100.) in
let _balance = ccp#withdraw (euro 50.) in
ccp#history;;
\end{caml_example}
Closing an account can be done with the following polymorphic function:
\begin{caml_example}{toplevel}
let close c = c#withdraw c#balance;;
\end{caml_example}
Of course, this applies to all sorts of accounts.

Finally, we gather several versions of the account into a module "Account"
abstracted over some currency.
\begin{caml_example*}{toplevel}
let today () = (01,01,2000) (* an approximation *)
module Account (M:MONEY) =
  struct
    type m = M.c
    let m = new M.c
    let zero = m 0.

    class bank =
      object (self)
        val mutable balance = zero
        method balance = balance
        val mutable history = []
        method private 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
      end

    class type client_view =
      object
        method deposit : m -> unit
        method history : m operation list
        method withdraw : m -> m
        method balance : m
      end

    class virtual check_client x =
      let y = if (m 100.)#leq x then x
      else raise (Failure "Insufficient initial deposit") in
      object (self)
        initializer self#deposit y
        method virtual deposit: m -> unit
      end

    module Client (B : sig class bank : client_view end) =
      struct
        class account x : client_view =
          object
            inherit B.bank
            inherit check_client x
          end

        let discount x =
          let c = new account x in
          if today() < (1998,10,30) then c # deposit (m 100.); c
      end
  end;;
\end{caml_example*}
This shows the use of modules to group several class definitions that can in
fact be thought of as a single unit.  This unit would be provided by a bank
for both internal and external uses.
This is implemented as a functor that abstracts over the currency so that
the same code can be used to provide accounts in different currencies.

The class "bank" is the {\em real} implementation of the bank account (it
could have been inlined). This is the one that will be used for further
extensions, refinements, etc.  Conversely, the client will only be given the client view.
\begin{caml_example*}{toplevel}
module Euro_account = Account(Euro);;
module Client = Euro_account.Client (Euro_account);;
new Client.account (new Euro.c 100.);;
\end{caml_example*}
Hence, the clients do not have direct access to the "balance", nor the
"history" of their own accounts. Their only way to change their balance is
to deposit or withdraw  money.  It is important to give the clients
a class and not just the ability to create accounts (such as the
promotional "discount" account), so that they can
personalize their account.
For instance, a client may refine the "deposit" and "withdraw" methods
so as to do his own financial bookkeeping, automatically.  On the
other hand, the function "discount" is given as such, with no
possibility for further personalization.

It is important to provide the client's view as a functor
"Client" so that client accounts can still be built after a possible
specialization of the "bank".
The functor "Client" may remain unchanged and be passed
the new definition to initialize a client's view of the extended account.
\begin{caml_example*}{toplevel}
module Investment_account (M : MONEY) =
  struct
    type m = M.c
    module A = Account(M)

    class bank =
      object
        inherit 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
      end

    module Client = A.Client
  end;;
\end{caml_example*}
\begin{caml_eval}
module Euro_account = Investment_account (Euro);;
module Client = Euro_account.Client (Euro_account);;
new Client.account (new Euro.c 100.);;
\end{caml_eval}
The functor "Client" may also be redefined when some new features of the
account can be given to the client.
\begin{caml_example*}{toplevel}
module Internet_account (M : MONEY) =
  struct
    type m = M.c
    module A = Account(M)

    class bank =
      object
        inherit A.bank
        method mail s = print_string s
      end

    class type client_view =
      object
        method deposit : m -> unit
        method history : m operation list
        method withdraw : m -> m
        method balance : m
        method mail : string -> unit
      end

    module Client (B : sig class bank : client_view end) =
      struct
        class account x : client_view =
          object
            inherit B.bank
            inherit A.check_client x
          end
      end
  end;;
\end{caml_example*}
\begin{caml_eval}
module Euro_account = Internet_account (Euro);;
module Client = Euro_account.Client (Euro_account);;
new Client.account (new Euro.c 100.);;
\end{caml_eval}


\section{s:modules-as-classes}{Simple modules as classes}

One may wonder whether it is possible to treat primitive types such as
integers and strings as objects. Although this is usually uninteresting
for integers or strings, there may be some situations where
this is desirable. The class "money"  above is such an example.
We show here how to do it for strings.

\subsection{ss:string-as-class}{Strings}

A naive definition of strings as objects could be:
\begin{caml_example}{toplevel}
class ostring s =
  object
     method get n = String.get s n
     method print = print_string s
     method escaped = new ostring (String.escaped s)
  end;;
\end{caml_example}
However, the method "escaped" returns an object of the class "ostring",
and not an object of the current class. Hence, if the class is further
extended, the method "escaped" will only return an object of the parent
class.
\begin{caml_example}{toplevel}
class sub_string s =
  object
     inherit ostring s
     method sub start len = new sub_string (String.sub s  start len)
  end;;
\end{caml_example}
As seen in section~\ref{s:binary-methods}, the solution is to use
functional update instead. We need to create an instance variable
containing the representation "s" of the string.
\begin{caml_example}{toplevel}
class better_string s =
  object
     val 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;;
\end{caml_example}
As shown in the inferred type, the methods "escaped" and "sub" now return
objects of the same type as the one of the class.

Another difficulty is the implementation of the method "concat".
In order to concatenate a string with another string of the same class,
one must be able to access the instance variable externally. Thus, a method
"repr" returning s must be defined. Here is the correct definition of
strings:
\begin{caml_example}{toplevel}
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;;
\end{caml_example}
Another constructor of the class string can be defined to return a new
string of a given length:
\begin{caml_example}{toplevel}
class cstring n = ostring (String.make n ' ');;
\end{caml_example}
Here, exposing the representation of strings is probably harmless.  We do
could also hide the representation of strings as we hid the currency in the
class "money" of section~\ref{s:friends}.

\subsubsection{sss:stack-as-class}{Stacks}

There is sometimes an alternative between using modules or classes for
parametric data types.
Indeed, there are situations when the two approaches are quite similar.
For instance, a stack can be  straightforwardly implemented as a class:
\begin{caml_example}{toplevel}
exception Empty;;
class ['a] stack =
  object
    val mutable 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;;
\end{caml_example}
However, writing a method for iterating over a stack is more
problematic.  A method "fold" would have type
"('b -> 'a -> 'b) -> 'b -> 'b". Here "'a" is the parameter of the stack.
The parameter "'b" is not related to the class "'a stack" but to the
argument that will be passed to the method "fold".
%The intuition is that method "fold" should be polymorphic, i.e. of type
%"All ('a) ('b -> 'a -> 'b) -> 'b -> 'b".
A naive approach is to make "'b" an extra parameter of class "stack":
\begin{caml_example}{toplevel}
class ['a, 'b] stack2 =
  object
    inherit ['a] stack
    method fold f (x : 'b) = List.fold_left f x l
  end;;
\end{caml_example}
However, the method "fold" of a given object can only be
applied to functions that all have the same type:
\begin{caml_example}{toplevel}
let s = new stack2;;
s#fold ( + ) 0;;
s;;
\end{caml_example}
A better solution is to use polymorphic methods, which were
introduced in OCaml version 3.05.  Polymorphic methods makes
it possible to treat the type variable "'b" in the type of "fold" as
universally quantified, giving "fold" the polymorphic type
"Forall 'b. ('b -> 'a -> 'b) -> 'b -> 'b".
An explicit type declaration on the method "fold" is required, since
the type checker cannot infer the polymorphic type by itself.
\begin{caml_example}{toplevel}
class ['a] stack3 =
  object
    inherit ['a] stack
    method fold : 'b. ('b -> 'a -> 'b) -> 'b -> 'b
                = fun f x -> List.fold_left f x l
  end;;
\end{caml_example}

% However, the nice correspondence between the implementations of stacks as
% modules or classes is a very particular case.

% XXX Maps

\subsection{ss:hashtbl-as-class}{Hashtbl}

A simplified version of object-oriented hash tables should have the
following class type.
\begin{caml_example}{toplevel}
class type ['a, 'b] hash_table =
  object
    method find : 'a -> 'b
    method add : 'a -> 'b -> unit
  end;;
\end{caml_example}
A simple implementation, which is quite reasonable for small hash tables is
to use an association list:
\begin{caml_example}{toplevel}
class ['a, 'b] small_hashtbl : ['a, 'b] hash_table =
  object
    val mutable table = []
    method find key = List.assoc key table
    method add key value = table <- (key, value) :: table
  end;;
\end{caml_example}
A better implementation, and one that scales up better, is to use a
true hash table\ldots\ whose elements are small hash tables!
\begin{caml_example}{toplevel}
class ['a, 'b] hashtbl size : ['a, 'b] hash_table =
  object (self)
    val table = Array.init size (fun i -> new small_hashtbl)
    method private hash key =
      (Hashtbl.hash key) mod (Array.length table)
    method find key = table.(self#hash key) # find key
    method add key = table.(self#hash key) # add key
  end;;
\end{caml_example}

% problem

% solution

\subsection{ss:set-as-class}{Sets}

Implementing sets leads to another difficulty.  Indeed, the method
"union" needs to be able to access the internal representation of
another object of the same class.

This is another instance of friend functions as seen in
section~\ref{s:friends}. Indeed, this is the same mechanism used in the module
"Set" in the absence of objects.

In the object-oriented version of sets, we only need to add an additional
method "tag" to return the representation of a set. Since sets are
parametric in the type of elements, the method "tag" has a parametric type
"'a tag", concrete within
the module definition but abstract in its signature.
From outside, it will then be guaranteed that two objects with a method "tag"
of the same type will share the same representation.
\begin{caml_example*}{toplevel}
module type SET =
  sig
    type '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
      end
  end;;
module Set : SET =
  struct
    let rec merge l1 l2 =
      match l1 with
        [] -> l2
      | h1 :: t1 ->
          match l2 with
            [] -> l1
          | h2 :: t2 ->
              if h1 < h2 then h1 :: merge t1 l2
              else if 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
      end
  end;;
\end{caml_example*}

\section{s:subject-observer}{The subject/observer pattern}

The following example, known as the subject/observer pattern, is often
presented in the literature as a difficult inheritance problem with
inter-connected classes.
The general pattern amounts to the definition a pair of two
classes that recursively interact with one another.

The class "observer"  has a distinguished method "notify" that requires
two arguments, a subject and an event to execute an action.
\begin{caml_example}{toplevel}
class virtual ['subject, 'event] observer =
  object
    method virtual notify : 'subject ->  'event -> unit
  end;;
\end{caml_example}
The class "subject" remembers a list of observers in an instance variable,
and has a distinguished method "notify_observers" to broadcast the message
"notify" to all observers with a particular event "e".
\begin{caml_example}{toplevel}
class ['observer, 'event] subject =
  object (self)
    val mutable observers = ([]:'observer list)
    method add_observer obs = observers <- (obs :: observers)
    method notify_observers (e : 'event) =
        List.iter (fun x -> x#notify self e) observers
  end;;
\end{caml_example}
The difficulty usually lies  in defining instances of the pattern above
by inheritance. This can be done in a natural and obvious manner in
OCaml, as shown on the following example manipulating windows.
\begin{caml_example}{toplevel}
type event = Raise | Resize | Move;;
let string_of_event = function
    Raise -> "Raise" | Resize -> "Resize" | Move -> "Move";;
let count = ref 0;;
class ['observer] window_subject =
  let id = count := succ !count; !count in
  object (self)
    inherit ['observer, event] subject
    val mutable 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 ['subject] window_observer =
  object
    inherit ['subject, event] observer
    method notify s e = s#draw
  end;;
\end{caml_example}
As can be expected, the type of "window" is recursive.
\begin{caml_example}{toplevel}
let window = new window_subject;;
\end{caml_example}
However, the two classes of "window_subject" and "window_observer" are not
mutually recursive.
\begin{caml_example}{toplevel}
let window_observer = new window_observer;;
window#add_observer window_observer;;
window#move 1;;
\end{caml_example}

Classes "window_observer" and "window_subject" can still be extended by
inheritance. For instance, one may enrich the "subject" with new
behaviors and refine the behavior of the observer.
\begin{caml_example}{toplevel}
class ['observer] richer_window_subject =
  object (self)
    inherit ['observer] window_subject
    val mutable size = 1
    method resize x = size <- size + x; self#notify_observers Resize
    val mutable top = false
    method raise = top <- true; self#notify_observers Raise
    method draw = Printf.printf "{Position = %d; Size = %d}\n"  position size;
  end;;
class ['subject] richer_window_observer =
  object
    inherit ['subject] window_observer as super
    method notify s e = if e <> Raise then s#raise; super#notify s e
  end;;
\end{caml_example}
We can also create a different kind of observer:
\begin{caml_example}{toplevel}
class ['subject] trace_observer =
  object
    inherit ['subject, event] observer
    method notify s e =
      Printf.printf
        "<Window %d <== %s>\n" s#identity (string_of_event e)
  end;;
\end{caml_example}
and attach several observers to the same object:
\begin{caml_example}{toplevel}
let window = new richer_window_subject;;
window#add_observer (new richer_window_observer);;
window#add_observer (new trace_observer);;
window#move 1; window#resize 2;;
\end{caml_example}

%\subsection{ss:Classes used as modules with inheritance}
%
% to be filled for next release...
%
% an example of stateless objects used to provide inheritance in modules
%


% LocalWords:  objectexamples bsection init caml val int Oo succ incr ref
% LocalWords:  typecheck leq bool cp eval sig struct ABSPOINT Abspoint iter neg
% LocalWords:  accu mem rec repr Euro euro ccp inlined ostring len concat OCaml