Style fonctionnel
Le style de la programmation objet 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 objet 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 lequel les valeurs
de certains champs de données sont changées.
Syntaxe
{< c1=v1; ...; cn=vn >}
On peut ainsi définir des points fonctionnels pour lesquels les
méthodes de mouvements n'ont pas d'effet de bord, mais renvoient un
nouveau point.
# class
f_point
p
=
object
inherit
point
p
method
f_moveto
(x1,
y1)
=
{<
x=
x1;
y=
y1>}
method
f_rmoveto
(dx,
dy)
=
{<
x=
x+
dx;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_moveto : int * int -> 'a
method f_rmoveto : int * 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
Le type des méthodes f_moveto et f_rmoveto ont un type résultat du
type de l'instance de la classe définie.
L'envoi de ces
dernières ne modifie pas l'objet originel.
# let
p
=
new
f_point
(1
,
1
)
;;
val p : f_point = <obj>
# print_string
(p#to_string())
;;
( 1, 1)- : unit = ()
# let
q
=
p#f_moveto
(2
,
2
)
;;
val q : f_point = <obj>
# print_string
(p#to_string())
;;
( 1, 1)- : unit = ()
# print_string
(q#to_string())
;;
( 2, 2)- : 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.
# print_string
((p#f_moveto
(3
,
3
))#to_string())
;;
( 3, 3)- : unit = ()
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 X).
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 u, 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.