Re: porte des definitions / initialisation des variables de classe

From: Serge Fantino (fantino@math.unice.fr)
Date: Wed Oct 07 1998 - 15:03:21 MET DST


Date: Wed, 07 Oct 1998 14:03:21 +0100
From: Serge Fantino <fantino@math.unice.fr>
To: Pierre Weis <Pierre.Weis@inria.fr>
Subject: Re: porte des definitions / initialisation des variables de classe

Bon. Je vais essayer de vous faire sentir ce qui me gene sur un exemple:

class test1 =
object
    val x = Random.int 1000
    method get = x
end

Je m'attends a ce que chaque nouvel objet cree a partir de test1 soit initialise
avec une valeur différente pour x.
En effet, l'initialisation est effectuee dans le corps de l'"object", elle ne
devrait donc etre effectuee que lorsque j'instancie un nouvel objet.
Or il n'en est rien:

List.map (fun _ -> (new test1)#get) [0;0;0;0;0;0]

retourne indifférement la meme valeur pour toutes les instances crees.
Par contre, si je declare la classe test2:

class test2 =
let random = Random.int 1000 in
object
    val x = random
    method get = x
end

alors effectivement, je m'attends au résultat obtenu.

 Ce n'est donc pas tand la porté des variables de classe qui me gene, mais le
deroulement de leur initialisation.
Tout ce qui est initialisé dans "object" devrait etre execute a chaque
instanciation, et non pas dépendre du "pattern" du constructeur.
(le fonctionnement de test1 et de test2 ne devrait pas etre equivalent...)

Ainsi, il n'y aurait pas de différence entre écrire "class test1 =" ou "class
test1 () =".
Par contre, il serait different d'ecrire "class test2 =" ou "class test2 () =", ce
qui est consistant avec le fonctionnement classique des fonctions en caml.
Actuellement, que ce passe-t-il ? Si je declare la classe test3:

class test3 range =
object
    val x = Random.int range
    method get = x
end

alors chaque nouvelle instance est initialisée avec une valeur différente de x, ie
l'initialisation est effectuée au moment de l'instanciation.

Si je declare maintenant:
class test3_1000 = test3 1000;;

il est logique de s'attendre a ce que test3_1000 soit une classe specialisée de
test3, fonctionnant de facon similaire a test3. Les expressions suivantes
devraient retourner des resultats similaires, ie des valeurs différentes <1000
pour chaque instance:
let l1 = List.map (fun _ -> (new test3 1000)#get) [0;0;0;0]
let l2 = List.map (fun _ -> (new test3_1000)#get) [0;0;0;0]

Helas, la liste l2 repete la meme valeur... pourquoi ?

Une facon equivalente de declarer test3_1000 est la suivante:
class test3_1000 =
object
    inherit test3 1000
end

Je trouve l'ambiguite encore plus criante sous cette forme, et encore plus
incomprehensible !

On peut voir sans difficulte qu'un tel fonctionnement n'est absoluement pas
consistant avec les regles d'evaluation classique de caml. Soit la version
fonctionnelle de l'exemple précedent:

let test3_constructeur range = (* correspond a la declaration class test3 range =
*)
    let test3_object () = (* construction d'une instance *)
        let x = Random.int range in (* declaration du val x *)
            fun () -> x (* methode get *)
    in
        test3_object (* la classe definie le constructeur de la classe *)

Alors il est equivalent d'ecrire:
(new test3 1000)
et:
(test3_constructeur range) ()

la classe specialise devient:
let test3_1000_constructeur = test3_constructeur 1000

et les expressions suivantes donnent un resultat similaire :

let l1 = List.map (fun _ -> ((test3_constructeur 1000) ()) ()) [0;0;0;0]
let l2 = List.map (fun _ -> ((test3_constructeur_1000) ()) ()) [0;0;0;0]

Voila, j'espere avoir ete plus clair et plus convainquant.

Cordialement,

Serge Fantino

Pierre Weis wrote:

> > Sylvain BOULM'E wrote:
> >
> > > Bonjour,
> > >
> > > Il me semble que c'est dans la semantique des classes
> > > de Ocaml, qui supportent desormais les "variables statiques de classes"
> > > partagees par tous les objets de la classe. (Les gens de Ocaml me
> > > corrigeront ...)
> > >
> > > Ainsi,
> > >
> > > class test1 =
> > > object
> > > val v = ref 5
> > > method get = !v
> > > method set x = v:=x
> > > end;;
> > >
> > > est equivalent a
> > >
> > > class test1 = let v1=ref 5 in
> > > object
> > > val v=v1
> > > ...
> > >
> > > Dans les 2 cas,
> > >
> > > let a=new test1 and b=new test1 in (b#set 4; a#get);;
> > >
> > > retourne 4.
> > >
> >
> > Effectivement, OCaml-2.0 semble fonctionné suivant ce schéma.
> > Je trouve cette "nouveauté" embarrassante:
>
> Je ne comprends pas le problème: cela ressemble fort à la sémantique
> habituelle du Caml de base...
>
> > - elle est ambigue;
>
> Je ne vois pas non plus en quoi elle l'est. Je ne connais pas grand
> chose aux objets mais ce comportement était celui que je prévoyais:
> la référence ref 5 est partagée dans les deux cas, comme dans le
> langage de base ...
>
> > Cela va a l'encontre du "sens commun". Les variables d'instance sont
> > declarees dans "object": je m'attends donc a ce que la porté de la variable
> > soit locale à une instance de la classe.
>
> C'est le cas. Je ne comprends pas. Ne confondez-vous pas partage des
> valeurs et portée des identificateurs ou variables ?
>
> > Lorsque je declare:
> >
> > class test1 =
> > object
> > val v = ref 5
> > method get = !v
> > method set x = v:=x
> > end;;
> >
> > je m'attends a ce que la variable v soit locale à chaque nouvel objet
> > construit à partir de (new test1).
>
> C'est le cas. Seulement vous avez tout fait pour partager la référence
> ref 5: pas de paramètre à la classe donc calcul fait une fois pour
> toutes donc allocation unique donc partage. À quoi d'autre
> pourrions-nous nous attendre ?
>
> > D'ailleurs, il est confondant de voir que la déclaration suivante:
> >
> > class test3 =
> > object
> > val mutable v = 5
> > method get = v
> > method set x = v<-x
> > end;;
> >
> > se comporte différement de test1 !
>
> Absolument pas. Il est rassurant que ça se comporte d'une façon
> différente, puisque maintenant la variable v est mutable!
>
> > - elle n'est pas consistante
> > Rien ne me previent du changement de fonctionnalité dans ce cas
> > particulier.
>
> Bien sûr que si: c'est cohérent car compatible avec toute la
> sémantique du langage de base (donc in fine avec celle de la réduction
> faible du lambda-calcul en appel par valeur). Enfin tout vous prévient
> de la différence entre les exemples: la présence du mot-clé mutable,
> l'absence de paramètre à la classe, le let ... in pour la deuxième
> version.
>
> > Il suffit de supprimer ou d'ajouter un paramètre à une classe pour en changer
> > complètement la sémantique.
> > Si je redefinis test1 :
> >
> > class test1 (x:int) =
> > object
> > val v = ref x
> > method get = !v
> > method set x = v:=x
> > end;;
> >
> > alors la variable v n'est plus partagée...
>
> Bien entendu. Rien d'étonnant là-dedans. Avez-vous remarqué combien la
> sémantique d'une fonction change quand on modifie son nombre de
> paramètres ? En particulier lorsqu'une fonction passe de 1 à zéro
> paramètres comme c'est le cas ici ?
>
> Comparez par exemple:
>
> let f () = exit 0;;
>
> let f = exit 0;;
>
> Vous devriez observer une différence de sémantique certaine. Remplacez
> classe par fonction dans votre phrase et vous obtenez:
>
> ``Il suffit de supprimer ou d'ajouter un paramètre à une fonction pour
> en changer complètement la sémantique.''
>
> Il me semble que ce problème de paramètre ajouté ou suprimé est
> général à beaucoup de constructions du langage.
>
> Blague: disons-le en Caml !
>
> let étonnement = function construction ->
> "Il suffit de supprimer ou d'ajouter un paramètre à " ^ construction ^
> " pour en changer complètement la sémantique.";;
>
> étonnement "une classe";;
> étonnement "une fonction";;
> étonnement "un module";;
> étonnement "un foncteur";;
> étonnement "un type";;
>
> etc..
>
> > - elle n'est pas necessaire;
> > Si on en a besoin, on peut simplement partager des variables "statiques"
> > entre différentes instances de la meme classe.
>
> Je laisse la parole aux spécialistes des objets.
>
> > Enfin c'est mon avis d'utilisateur.
>
> Et il est très intéressant. Merci de nous le faire partager. Je crois
> qu'il montre aux implémenteurs de Caml qu'il y a sans doute un décifit
> d'explications sur le langage en général et sur ses objets en particulier.
>
> > Pour la petite histoire, il m'a fallu
> > tout un weekend pour comprendre pourquoi mon code ne marchait plus sous
> > OCaml-2.0, alors que la version 1.07 marche tres bien...
>
> Peut-être encore ce déficit d'explications ...
>
> > Objectivement votre,
> >
> > Serge Fantino
>
> Cordialement,
>
> Pierre Weis
>
> INRIA, Projet Cristal, Pierre.Weis@inria.fr, http://cristal.inria.fr/~weis/



This archive was generated by hypermail 2b29 : Sun Jan 02 2000 - 11:58:16 MET