Précédent Index Suivant

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.


Précédent Index Suivant