Précédent Index Suivant

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.


Précédent Index Suivant