Author: Christian.Queinnec@lip6.fr

## 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.