3 Structure du langage
3.1 For
La structure est ‘for in endFor’. Si on est sur une ligne, le endFor est optionnel. On doit conserver la possibilité de faire des for sur des vecteurs, y compris des vecteurs d'objets complexes (comme un vecteur de fonction par exemple). La boucle for doit rester simple : pas de double déclaration de variable, pas d’incrémentation de plusieurs variable. Pour les choses trop compliqué, on passe sur du while. On peut déclarer la variable compteur directement dans la boucle. La variable est typée de la manière suivante : d’abord on type ce qu’elle doit parcourir à savoir "vecteur de XXX". La variable est du type XXX.
1 2 3 4
| > for i in 1:5 do
+ a <- a+i^2
+ endFor
> for i in 1:5 do a <- a+i^2 |
3.2 If else
La structure du if impose une fin de if sauf si le if tient sur une ligne.
1 2 3 4 5 6 7 8 9
| > if a==3 then
+ blabla
+ else
+ blibli
+ endIf
> if a==3 then
+ blabla
+ endIf |
Variante sur une ligne
> if a==3 then blabla
> if a==3 then blabla else blibli
3.3 Break et continu
Break doit pouvoir permettre de sortir de plusieurs boucles (ou structures) imbriquées. Une solution est de mettre un argument indiquant le nombre de break :
1 2 3 4
| > break(2) // sort de la premier boucle et de la deuxième
> break.continu // sort de la premier et continue la deuxième
> continu.break // n’a pas de sens => erreur
> break(2).continu // sort de deux boucles, continue la troisième. |
[[[Est-ce qu’on conserve ce genre de chose ou est-ce un peu crade ? En tout cas, ca remplace bien les labels sous java.]]]
3.4 Appel des fonctions
Il faut donner le choix entre passage par valeur et par pointeur. Cela se fait dans la déclaration de la fonction. Le programmeur peut soit spécifier par valeur, soit par référence, soit ne rien spécifier du tout. S'il ne spécifie pas, c'est le compilateur qui décidera : si la variable est modifiée dans le corps de la fonction, alors la transmission était par valeur, si elle n'est pas modifié, elle était par référence.
1 2 3 4 5 6 7 8 9
| integer toto <- function (integer ref x, integer val y, integer z)
z <- x+y
return z
endFunction
integer titi <- function (integer ref x, integer val y, integer z)
x <- z+y
return x
endFunction |
Dans cet exemple, z pour toto est passé par valeur, z pour titi est passé par référence.
3.5 Virgule finale (en vert, ce qui a été rendu obsolète par un autre point, ou par une réponse)
On autorise-t-on une virgule (ou point virgule si la virgule devient point virgule) après le dernier élément d’une énumération. Ca peut être pratique dans certains cas (quand on commente ou qu’on intervertir l’ordre) de ne pas avoir a ajouter ou supprimer cette virgule de la dernière ligne :
1 2 3 4 5
| switch
case(x>3) : blabla
case(x<1) : blabla
case(x>2) : blabla
endSwitch |
3.6 Virgule et plus
ATTENTION : la virgule doit avoir un sens unique. Dans R, elle sépare les arguments mais elle est aussi utilisée pour une liste. Ainsi mean(1,2) ne fonctionne pas car 2 est en fait le deuxième argument.
Il faut donc deux séparateurs distincts, un pour les arguments et un pour les listes. Cela pourrait être :
1 2
| > mean(1,2,3;na.rm=true)
> plot(1,2,3; col=2,4; ltype="l") |
Dans cette optique, le ‘ ;’ n’est plus un séparateur d’instruction. Le séparateur d’instruction peut être ‘;;’. Rappel, dans tous les cas, le ‘ ;’ de fin de ligne n’est plus obligatoire.
L’autre solution serait de mettre les listes entre accolade ou parenthèses :
> mean((1,3,2),na.rm=true) // alternative
3.7 Operateur ‘?’
expression-booléenne ? valeur0 : valeur1
peut être généralisé à
expression-integer ? valeur0 : valeur1 : valeur2 : valeur3
3.8 Opérateur + (et autre)
Il faut pouvoir les redéfinir (contrairement à Java qui ne le permet pas).
3.9 Return
Impose-t-on le return en fin de fonction ? Impose-t-on return void pour les fonctions qui ne retournent aucun argument ?
3.10 Switch
Plusieurs version du switch. Le switch "classique" est :
1 2 3 4 5
| switch toto is
case "E" then blabla
case "F" then blabla
else blabla
finSwitch |
Pas besoin de finCase en fin de ligne (ou de break) car le ‘case’ suivant, ou le ‘default’ en fait office. Le fonctionnement de ce premier switch est le suivant : des que la valeur de toto correspond à un case, les instructions correspondantes sont exécutées, puis le switch se termine (comme s'il y avait un break en java).
On peut aussi supprimer l’argument de switch si on utilise que des case avec conditions complètes :
1 2 3 4 5 6
| switch
case toto=="E" then blabla
case toto==”G” and titi>6 then bloblo
case titi>3 then blibli
else blublu
finSwitch |
Deux autres switch sont disponibles :
- SwitchAll est un switch ou toutes les conditions vraies sont exécutées. Dans le code précédent, si toto vaut "E" et titi vaut 5, alors blabla et bloblo seront exécutés.
- SwitchCont est un switch ou si une condition est vraie, toutes suivantes sont exécutées.Dans le code précédent, si toto vaut "E", alors blabla, bloblo et blibli sont exécuté (mais pas le else)
3.11 Portée des variables
Interdiction de déclarer une variable déjà existante. Ainsi :
1 2 3 4 5 6
| > int i=2
i.m()
if(){
float i=3.0
i.m()
}else{} |
n’est pas possible (contrairement à C ou le deuxième i cache le premier dans le block, puis disparait quand on en sort).
Note : le polymorphisme permet de déclarer la méthode m() sur des int mais également sur des float. Du coup, dans le code précédent, l’interprétation de i.m() est dynamique car elle dépend du type de i. Si on interdit d’utiliser i deux fois, alors le problème ne se pose plus :
1 2 3 4 5 6
| int i1=2
i1.m()
if(){
float i2=3.0
i2.m()
}else{} |
i1.m() fera appel à la méthode m() pour int alors que i2.m() fera appel à la méthode m() pour float.
Pour les fonctions, le problème se pose :
int i=2
f <- function(){
float i=3.0
i.m()
}
Mais on pourrait décider qu’un pré-processeur recode les noms des variables en « nom-de-la-fonction_non-de-la-variable ». Dans notre cas, le code :
1 2 3 4 5 6
| i <- 2
i.m()
void f <- function(){
i <- 3.0
i.m()
} |
serait transformé en :
1 2 3 4 5 6
| i <- 2
i.m()
void f <- function(){
f_i <- 3.0
f_i.m()
} |
3.12 Egal
Pas de '=' en R++ (à moyen terme. Au départ, il sera accepté avec un Warning) sauf dans les appels de fonction.
- L’affectation par copie (intuitive) se fera via ‘<-‘
- L’affectation par référence (économique mais plus difficile à manipuler) se fera via ‘<<-‘
- l’opérateur d’égalité sera ‘==’
On pourrait envisager de définir A<-*3 (ou A<* 3 ?) , B<-+5 (ou B<+ 5 ?) qui serait équivalent a A<-A*3 ou B<-B+5. Mais ce genre de notation rend le code moins lisible. Donc a priori, non.
Enfin, autorise-t-on les <- en cascade ?
3.13 Affectation
Il est important que l’affectation par défaut soit par copie. Sinon, on risque d’avoir :
1 2 3 4 5 6
| > y <- 3
> x <- 5
> y <- x # affectation par valeur
> x <- 8
> print(y)
[1] 8 # On veut 5 !!! |
Par contre :
1 2 3 4 5 6 7 8 9 10
| > y <- 3
> z <- 2
> x <- 5
> y <- x # affectation par référence
> z <<- x # affectation par valeur
> x <- 8
> print(y)
[1] 5 # car y est une variable différente de x
> print(z)
[1] 8 # car z est pointe la même chose que x |
Concernant la comparaison ‘==’, elle renvoie généralement faux (en C, en R et en Java) si les champs sont égaux mais que les adresses des objets pointés sont différentes. Il serait mieux de faire l’inverse (c’est souvent ce que l’utilisateur veut quand il tape ==). Pour les égalités strictes, on définit l'opérateur ‘===’.
Au final :
1 2 3 4 5 6
| > y <- Toto(3) // y est de type Toto et contient 3
> x <- y // x est de type Toto, contient 3 mais est différent de Y
> x==y // égalité de contenu
[1] true
> x===y // égalité stricte
[1] false |
3.14 Fonctions
Il faut conserver la possibilité d’écrire des fonctions qui ne seront pas spécifiquement attacher à un objet et qui s'utiliseront comme des fonctions (c'est a dire f(toto) et non pas toto.f()). On pourrait décider que ces fonctions sont des méthodes de la classe Objet (la classe dont hérite toutes les classes).
1 2
| > numeric f <- function(numeric x) x^3+3x^2+5x-1
> plot(1:10,f(1:10)) |
Partager