Daniel Mailharro wrote:

> Salut,
> 
> Je suis en train de lire ton code. C'est tres tres clair !

Merci.

> Quelques questions cependant:
> 
> - backend:
>   1)  Peux tu m'expliquer RuntimeTuple ?
>       Il y a 3 tableaux (int[], double[], Object[]).
>       Est-ce que ca veut dire que tu ne supportes que des tuples de type
>        homogenes (que des int, ou que des double, ou que des Object) ?
>       Ou est-ce que dans le cas de tuples heterogene, tu boxes ?
>       Ou est-ce autre chose encore ?

Tous genres de tuples sont admis. Les trois tableaux int[], double[],
Object[]) ne sont que les projections du tuple sur ses composantes int,
double, ou Object, respectivement. Par exemple, le tuple heterogene t =
<1.2,3,"a","b",4.56,7,"c",8> est represente par les trois tableaux:

        t.intComponents()    = [3,7,8]
        t.realComponents()   = [1.2,4.56]
        t.objectComponents() = ["a","b","c"].

Cette trichotomie est la meme omnipresente partout dans l'implementation
Java du systeme car on ne peut pas avoir des ints, doubles, et pointeurs
dans un meme tableau. En C++, ce n'est bien sur pas necessaire d'avoir
trois tableaux - un seul tableau suffirait : soit en etablissant des
pointeurs sur des ints et doubles primitifs ; soit en utilisant une
architecture taggee pour int, double, et pointeur. La premiere option
est plus simple a mettre en oeuvre mais le prix est une dereference
systematique pour tout acces a un element. La deuxieme option est plus
difficile a implementer car il s'agit de representer des ints et doubles
sur un format consacrant 2 ou 3 bits pour un masque identifiant la
sorte.  Ceci etant, il est aussi concevable de garder dans un backend
C++ la meme trichotomie qu'en Java (1) pour plus de simplicite, au moins
dans un premier temps; (2) cela offre un compromis interessant entre les
deux autres options car cela facilite l'ajout d'un glaneur de cellule
(manquant en C++) de moindre complexite a implementer que pour une
architecture taggee classique.

>   2) Je ne comprends pas bien le concept de Closure ?
>      Pourquoi ca derive de Block ?
>      C'est quoi une "frame machinery" ?

Une "closure" est une representation d'une lambda-abstraction au runtime
- c.a.d., une fonction + la valeur de ses variables libres.  Les noms
des variables n'existent pas au runtime, car leurs occurrences sont
remplacees par des indices de "de Bruijn" (l'indice de de Bruijn d'une
occurrence de variable est un entier #i indiquant que la distance entre
cette occurrence et le lambda qui l'introduit est egale a i). Par
exemple,

        lambda(x).apply(lambda(y).x+y,
                        x)

devient :

        lambda.apply(lambda.#1+#0,
                     #0)

Tu remarques que les indices des occurrences d'une meme variable a
differents niveaux d'imbrication sont differents (e.g., la variable x
ci-dessus). Le meme indice peut donc indiquer des variables differentes
(e.g., la variable y et la deuxieme occurrence de x ci-dessus).  C'est
parce que l'indice est l'offset au runtime de la valeur de la variable
dans l'environnement (qui est une pile). Un tel offset #i est donc une
occurrence d'une variable non-libre et correspond a l'instruction
PUSH_OFFSET_[I,R,O] i. Cette instruction indique que la valeur actuelle
de la variable est a un deplacement i dans l'environnement. Mais qu'en
est-il des variables non-libres mais qui ne sont pas globales ? Il faut
les preserver dans un "frame" qui est la partie de l'environnement
contenant leurs valeurs et encapsuler ce "frame" dans une closure de la
forme closure(fonction,frame). Ce frame peut ainsi etre "encapsule" dans
la fonction. L'instruction 'APPLY' concatene ce frame a l'environnement
courant et les indices sont donc toujours coherents.

Prends par exemple, l'expression :

        let x = 1 in
            let f = lambda(y).x+y in
                let x = 2 in
                    f(3)

qui est la meme que :

        apply(lambda(x).apply(lambda(f).apply(lambda(x).apply(f,3),
                                              2),
                              lambda(y).x+y),
              1).
        
Avec les indices de de Bruijn, elle devient :

        apply(lambda.apply(lambda.apply(lambda.apply(#1,3),
                                        2),
                           lambda.#1+#0),
              1).

Avec les encapsulations de frames en closures, elle deviendra
equivalente a l'evaluation de :

        apply(closure(apply(closure(apply(closure(apply(#1,3),
                                                  [closure(#1+#0,[1]]),
                                          2),
                                    []),
                            closure(#1+#0,[1])),
                      []),
              1).

le resultat de l'evaluation de cette expression est 4 (et non 5) car la
representation de 'lambda(y).x+y' a encapsule la valeur de sa variable
libre (x = 1) en une "closure" : closure(#1+#0,[1]).

Un block est la meme chose qu'une closure, mais sans frame. C'est utile
pour deux raisons:

        (1) Il y a enormement de closures dont le frame est vide (par
            exemple, sur les 4 closures ci-dessus, seulement une a un
            frame non vide) ; autant economiser un pointeur.

        (1) En fait, un 'let' donnera toujours un block, pas une
            closure, meme si il contient des variable libres. C'est
            parce qu'un block veut pouvoir affecter (par effet de bord)
            une valeur directement a une variable locale dans
            l'environnement qu'il partage avec l'expression qui le
            contient ; une closure aussi, sauf que ces effets de bords
            sont locaux et ne persistent pas hors de la closure - ce qui
            serait ennuyeux pour un 'let' - par exemple :

                let x = 1 in
                    { write (x);        // writes 1
                      let y = 2 in
                          { x = x+1;
                            write(x);   // writes 2
                          };
                      write (x);        // writes ???
                    }

            Si les 'let's sont traduits comme d'habitude en apply/closure,
            on aurait :
            
                let x = 1 in
                    { write (x);        // writes 1
                      let y = 2 in
                          { x = x+1;
                            write(x);   // writes 2
                          };
                      write (x);        // writes 1 - unexpected!
                    }

            ce qui perdrait l'affectation faite a x dans le block de y.
            En utilisant enter/block au lieu de apply/closure pour les
            'let's, on obtient ce qu'on veut :

                let x = 1 in
                    { write (x);        // writes 1
                      let y = 2 in
                          { x = x+1;
                            write(x);   // writes 2
                          };
                      write (x);        // writes 2
                    }

D'ou la veritable traduction de l'exemple d'en haut, non en :

        apply(closure(apply(closure(apply(closure(apply(#1,3),
                                                  [closure(#1+#0,[1]]]),
                                          2),
                                    []),
                            closure(#1+#0,[1])),
                      []),
              1)

mais en :

        enter(block(enter(block(enter(block(apply(#1,3)),
                                      2)),
                                closure(#1+#0),[1]),
              1).

>   Le reste est Ok.
> 
> - Instructions:
>   1) Dans SlicingInstruction,
>      a quoi correspond le tableau int[][] _slices ?

Il contient un tableau de "paths" (des tableaux d'ints qui sont les
projections composees des occurrences dans les tuples). Par exemple,
imagine qu'on ait deux slicers sur une variable x : x.1.2 = e1 et
x.2.3.2 = e2. Le tableau _slice dans ce cas est donc [[1,2],[2,3,2]].

Ces tableaux d'ints (les "paths") ont tous un element supplementaire a
la fin qui donne la sorte "runtime" de la valeur au bout du "path"
(i.e., int, double, ou Object).


>   2) Dans ApplySlicedObjectHomomorphism,
>       tu pop des slicers: C'est quoi ces objets "slicer" ?
>       ensuite tu utilises un sliceIterator sur la collection. Comment
>       marche cet iterateur ?

Dans l'exemple au dessus, les slicers seront les resultats de
l'evaluation de e1, et e2. L'iterateur sliceIterator(_slices,slicers)
d'une collection de tuples construit une representation "inversee" de
la collection par rapport aux valeurs des slicers en construisant une
slicedMap si necessaire pour la collection qui hache d'abord sur x.1.2 =
e1, puis sur x.2.3.2 = e2.

> C'est tout pour aujourd'hui.

OK.

> Je continue demain.

Bon courage.

> Merci pour tes reponses qui m'aideront a avancer.

Je l'espere.

> Daniel

-hak