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.