Previous Contents Next

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.


Previous Contents Next