Functional Style
Object-oriented programming usually has an imperative style. A message is
sent to an object that physically modifies its internal state (i.e. its data
fields). It is also possible to use a functional approach to
object-oriented programming: sending a message returns a new object.
Object Copy
Objective CAML provides a special syntactic construct for returning a copy of an object
self with some of the fields modified.
Syntax
{<
name1=expr1;...;
namen=exprn
>}
This way we can define functional points where methods for relative moves
have no side effect, but instead return a new point.
# class f_point p =
object
inherit point p
method f_rmoveto_x (dx) = {< x = x + dx >}
method f_rmoveto_y (dy) = {< y = y + dy >}
end ;;
class f_point :
int * int ->
object ('a)
val mutable x : int
val mutable y : int
method distance : unit -> float
method f_rmoveto_x : int -> 'a
method f_rmoveto_y : int -> 'a
method get_x : int
method get_y : int
method moveto : int * int -> unit
method print : unit -> unit
method rmoveto : int * int -> unit
method to_string : unit -> string
end
With the new methods, movement no longer modifies the receiving object; instead a new object
is returned that reflects the movement.
# let p = new f_point (1,1) ;;
val p : f_point = <obj>
# print_string (p#to_string()) ;;
( 1, 1)- : unit = ()
# let q = p#f_rmoveto_x 2 ;;
val q : f_point = <obj>
# print_string (p#to_string()) ;;
( 1, 1)- : unit = ()
# print_string (q#to_string()) ;;
( 3, 1)- : unit = ()
Since these methods construct an object, it is possible to send a message
directly to the result of the method f_rmoveto_x.
# print_string ((p#f_rmoveto_x 3)#to_string()) ;;
( 4, 1)- : unit = ()
The result type of the methods f_rmoveto_x and f_rmoveto_y
is the type of the instance of the defined class, as shown by the 'a
in the type of f_rmoveto_x.
# class f_colored_point (xc, yc) (c:string) =
object
inherit f_point(xc, yc)
val color = c
method get_c = color
end ;;
class f_colored_point :
int * int ->
string ->
object ('a)
val color : string
val mutable x : int
val mutable y : int
method distance : unit -> float
method f_rmoveto_x : int -> 'a
method f_rmoveto_y : int -> 'a
method get_c : string
method get_x : int
method get_y : int
method moveto : int * int -> unit
method print : unit -> unit
method rmoveto : int * int -> unit
method to_string : unit -> string
end
Sending f_rmoveto_x to an instance of f_colored_point
returns a new instance of f_colored_point.
# let fpc = new f_colored_point (2,3) "blue" ;;
val fpc : f_colored_point = <obj>
# let fpc2 = fpc#f_rmoveto_x 4 ;;
val fpc2 : f_colored_point = <obj>
# fpc2#get_c;;
- : string = "blue"
One can also obtain a copy of an arbitrary object, using the the primitive
copy from module Oo:
# Oo.copy ;;
- : (< .. > as 'a) -> 'a = <fun>
# let q = Oo.copy p ;;
val q : f_point = <obj>
# print_string (p#to_string()) ;;
( 1, 1)- : unit = ()
# print_string (q#to_string()) ;;
( 1, 1)- : unit = ()
# p#moveto(4,5) ;;
- : unit = ()
# print_string (p#to_string()) ;;
( 4, 5)- : unit = ()
# print_string (q#to_string()) ;;
( 1, 1)- : unit = ()
Example: a Class for Lists
A functional method may use the object itself, self, to compute the
value to be returned. Let us illustrate this point by defining a simple
hierarchy of classes for representing lists of integers.
First we define the abstract class, parameterized by the type of list
elements.
# class virtual ['a] o_list () =
object
method virtual empty : unit -> bool
method virtual cons : 'a -> 'a o_list
method virtual head : 'a
method virtual tail : 'a o_list
end;;
We define the class of non empty lists.
# class ['a] o_cons (n ,l) =
object (self)
inherit ['a] o_list ()
val car = n
val cdr = l
method empty () = false
method cons x = new o_cons (x, (self : 'a #o_list :> 'a o_list))
method head = car
method tail = cdr
end;;
class ['a] o_cons :
'a * 'a o_list ->
object
val car : 'a
val cdr : 'a o_list
method cons : 'a -> 'a o_list
method empty : unit -> bool
method head : 'a
method tail : 'a o_list
end
We should note that method cons returns a new instance of
'a o_cons. To this effect, the type of self is constrained
to 'a #o_list, then subtyped to 'a o_list. Without
subtyping, we would obtain an open type ('a #o_list), which appears in the
type of the methods, and is strictly forbidden (see page
??). Without the additional constraint, the type of
self could not be a subtype of 'a o_list.
This way we obtain the expected type for method cons. So now we know
the trick and we define the class of empty lists.
# exception EmptyList ;;
# class ['a] o_nil () =
object(self)
inherit ['a] o_list ()
method empty () = true
method cons x = new o_cons (x, (self : 'a #o_list :> 'a o_list))
method head = raise EmptyList
method tail = raise EmptyList
end ;;
First of all we build an instance of the empty list, and then a list of integers.
# let i = new o_nil ();;
val i : '_a o_nil = <obj>
# let l = new o_cons (3,i);;
val l : int o_list = <obj>
# l#head;;
- : int = 3
# l#tail#empty();;
- : bool = true
The last expression sends the message tail to the list containing the
integer 3, which triggers the method tail from the class 'a o_cons.
The message empty(), which returns true, is sent to this result.
You can see that the method which has been executed is empty from
the class 'a o_nil.