Style fonctionnel
Le style de la programmation par objets est le plus souvent impératif. Un
message est envoyé à un objet qui modifie physiquement son état
interne (ses champs de données). Néanmoins il est aussi possible
d'aborder la programmation par objets par le style fonctionnel. L'envoi
d'un message à un objet retourne un nouvel objet.
Copie d'objets
Objective CAML offre une construction syntaxique particulière qui permet
de retourner une copie de l'objet self dans laquelle les valeurs
de certains champs de données sont changés.
Syntaxe
{<
nom1=expr1;...;
nomn=exprn
>}
On peut ainsi définir des points fonctionnels pour lesquels les
méthodes de mouvements relatifs n'ont pas d'effet de bord, mais renvoient un
nouveau 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
L'envoi des nouvelles méthodes de déplacement ne modifie pas l'objet
receveur.
# 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 = ()
Comme ces méthodes construisent un objet, il est possible d'envoyer
directement un message au résultat de la méthode f_rmoveto_x.
# print_string
((p#f_rmoveto_x
3
)#to_string())
;;
( 4, 1)- : unit = ()
Les méthodes f_rmoveto_x et f_rmoveto_y
ont un type résultat du type de l'instance de la classe
définie. C'est ce qu'indique le 'a du type de
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
L'envoi de f_rmoveto_x à une instance de
f_colored_point retourne une nouvelle instance de
f_colored_point.
# let
fpc
=
new
f_colored_point
(2
,
3
)
"bleu"
;;
val fpc : f_colored_point = <obj>
# let
fpc2
=
fpc#f_rmoveto_x
4
;;
val fpc2 : f_colored_point = <obj>
# fpc2#get_c;;
- : string = "bleu"
On peut également obtenir une copie de n'importe quel objet en
utilisant la primitive copy du 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 = ()
Exemple : classe pour les listes
Une méthode fonctionnelle peut utiliser l'objet lui-même
self pour le calcul de sa valeur de retour.
Nous illustrons ce point en définissant une hiérarchie simple de
classes pour représenter les listes d'entiers.
On commence par définir la classe abstraite paramétrée par le type des éléments des listes.
# 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;;
On définit la classe des listes non vides.
# 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
Il est à noter que la méthode cons retourne une nouvelle
instance de 'a o_cons. Pour ce faire le type de
self est contraint à 'a #o_list puis sous-typé en
'a o_list. Sans le sous-typage, on obtient un type ouvert
('a #o_list) qui apparaît dans le type des méthodes, ce qui
est rigoureusement interdit (voir page ??). Sans
la contrainte ajoutée le type de self ne peut pas être
sous-type de 'a o_list.
On obtient de cette façon le type attendu pour la méthode
cons. Et maintenant que l'on connaît le principe, on définit
la classe des listes vides.
# 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
;;
On construit tout d'abord une instance de la liste vide, pour ensuite créer une liste d'entiers.
# 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
La dernière expression envoie le message tail sur la liste contenant l'entier 3,
qui déclenche la méthode tail de la classe 'a o_cons. Sur
ce résultat (la liste vide i) est ensuite envoyé
le message empty() qui retourne
true. C'est bien la méthode empty de la classe 'a o_nil qui est exécutée.