Le langage Scala

Bases du langage

Cette section a pour objectif de présenter les bases du langages Scala, afin que les morceaux de code utilisés pour illustrer les concepts plus complexes soient compréhensible.

Syntaxe et philosophie

Le langage Scala propose trois paradigmes de programmation :

Le Scala est un langage à typage statique, c'est-à-dire que toutes les variables manipulées ont un type. Ce choix a été fait pour que les erreurs de programmation soient détectées à la compilation. Le langage Scala offre cependant les avantages du typage dynamique : lorsque le compilateur peut deviner le type d'une variable, il n'est pas nécessaire de la spécifier. Ce concept est expliqué en détails dans la section sur les variables ci-dessous.

En Scala, tout est vraiment objet contrairement au langage Java. En effet, les types primitifs du langage comme les entiers ou les booléens sont des classes (Int et Boolean, respectivement). Le langage Scala étant un langage objet, une variable d'un type peut accepter soit une instance de ce type, soit une instance d'une classe qui hérite de ce type.

Enfin, la syntaxe du langage Scala se veut la plus légère possible. Par exemple, les points-virgule à la fin d'une instruction n'est pas obligatoire. D'autres exemples de caractères et de mots-clés facultatifs en Scala sont donnés dans la suite de cette partie.

Variables

En Scala, une nouvelle variable «i» de type entier peut être initialisée à la valeur 42 de la façon suivante

scala> val i : Int = 42
i: Int = 42

Le langage Scala propose deux catégories de variable : les variables dont la valeur peut changer et celles dont la valeur ne peut changer. La deuxième catégorie est comparable aux variables déclarées avec le mot-clé «final» en Java. Cette catégorie de variable est porposée par Scala pour permettre aux développeurs de faire de la programmation fonctionnelle avec des données non-mutables. Une variable mutable peut être déclarée avec le mot-clé «var», alors qu'une variable non-mutable est déclarée avec le mot-clé «val». L'exemple suivant met en évidence cette différence :

scala> val i1 : Int = 42
i1: Int = 42

scala> i1 = 43
<console>:8: error: reassignment to val
       i1 = 43
          ^

scala> var i2 : Int = 42
i2: Int = 42

scala> i2 = 43
i2: Int = 43

Ci-dessus, la variable i1 est déclarée comme une variable non-mutable; sa réassignation est donc impossible et l'interpréteur retourne une erreur. Au contraire, la variable i2 est mutable et l'interpréteur accepte qu'une nouvelle valeur lui soit assignée.

Contrairement aux langages C et Java, le type d'une variable est spécifié après son nom lors de sa déclaration. Le nom de la variable et son type doivent être séparés par le caractères «deux points». Cependant, il est tout à fait possible d'omettre le type si celui-ci est évident. L'exemple suivant montre un cas où le développeur laisse le langage deviner le type, puis un deuxième cas où il force l'utilisation d'un Float.

scala> val i = 42
i: Int = 42

scala> val f : Float = 42
f: Float = 42.0

Fonctions

En Scala, les fonctions s'écrivent de la façon suivante :

def square(value : Int) : Int = {
	return value * value
}

Cette notation comporte :

Jusqu'ici, il n'y a rien de surprennant pour un programmeur habitué à des langages comme le C ou le Java. Cependant, cette même fonction peut s'écrire de la façon suivante, qui peut paraître un peu plus déroutante :

def square(value : Int) = value * value

Tout d'abord, le type de retour n'a pas besoin d'être précisé et sera déduit lors de la compilation. Ensuite, les accolades sont elles-aussi facultatives quand le corps de la fonction ne contient qu'une opération. Enfin, le mot-clé return n'est lui aussi pas indispensable : en effet, la dernière valeur utilisée dans la fonction sera celle renvoyée, même si c'est une valeur retournée par une fonction

Les classes

Scala propose le paradigme de programmation orienté objet. Ainsi, les programmes Scala sont organisés en classes. La définition de ces classes est très proche de Java. Les méthodes de classes s'écrivent de la même manière que les fonctions présentées précédemment.

L'exemple suivant défini une classe représentant un chat qui peut être tué au travers de sa méthode kill() jusqu'à ce qu'il ait épuisé ses sept vies. La méthode isDead() de cette classe renvoie vrai lorsque le nombre de vie restante est égal à zéro.

class Cat(private val name : String) {
	private var numberOfLives : Int = 7
	
	def getName() = name
	
	def kill() {
		if(numberOfLives >= 0) numberOfLives -= 1
	}
	
	def isDead() = numberOfLives == 0
}

Certains détails du code ci-dessus ont besoin d'être expliqués :

Comme en Java, les classes sont instanciées grâce au mot-clé «new» suivi du nom de la classe et enfin, de ses paramètres.

«Singleton objects» et «Companion object»

Le langage Scala ne permet pas de définir des méthodes ou des attributs statiques dans une classe. Pour faire cela, il faut utiliser un type de classe particulier qui est instancié par défaut et ne peut être réinstancié. Scala utilise le nom de «singleton object» pour définir ce type de classe. La définition d'un singleton object est similaire à la définiton d'une classe, sauf qu'il faut utiliser le mot-clé «object» à la place du mot-clé «class».

C'est typiquement dans un singleton object que va se trouver la méthode main, c'est-à-dire le point d'entrée du programme. Le code suivant montre la définition d'un singleton object contenant une méthode main utilisant les classe Cat définie précédemment :

object test {
	def main(argv : Array[String]) {
		var myCat = new Cat("Pima")
		while(!myCat.isDead) {
			println(myCat.getName + " is not dead")
			myCat.kill()
		}
		println(myCat.getName + " is dead :'-(")
	}
}

Dans le code précédent, la classe Cat est instanciée en donnant le nom «Pima» au chat. Celui-ci est ensuite tué jusqu'à ce qu'il ait épuisé ses sept vies.

Les singleton objects ne résolvent que la moitié du problème : parfois, certains attributs ou méthodes statiques appartiennent vraiment à une classe et non pas de raison d'être séparés de celle-ci. Pour cela, Scala propose les «companion objects», qui sont des singleton objects ayant le même nom qu'une classe. Les companion objects d'une classe ont une visibilité totale sur sa classe, et inversement. Pour des raisons de lisibilité, une classe et son companion object sont écrits dans le même fichier (ce qui est tout à fait autorisé en Scala).

Les boucles

Sans surprise, le langage Scala propose la structure de contrôle if/else pour faire des tests simples que n'importe quel programmeur n'aura aucun mal à utiliser :

if(i == 0)
	println("i equals zero")
else
	println("i does not equal to zero")

Le fait que le Scala soit un langage impératif ET fonctionnel nécessite la présence de deux types de boucles : les boucles sur des objets mutables et les boucles sur des objets non-mutables. Les deux prochaines sous-sections s'attachent à présenter la même boucle dans les deux paradigmes

La boucle while

Le langage Scala ne propose pas la boucle for dans sa forme la plus courante codée ci-dessous en C :

/* Ce n'est pas du Scala ! */
int i;
for(i=0 ; i<10 ; i++)
	printf("%d\n", i);

En Scala, il faut utiliser la boucle while. L'exemple suivant, écrit en Scala cette fois-ci fait la même chose que précédemment :

var i : Int = 0
while(i < 10) {
	println(i)
	i = i+1
}

Plus long et moins lisible n'est-ce pas ? En réalité, le choix de ne pas proposer cette forme de boucle for a été fait en Scala pour privilégier sa forme fonctionnelle, décrite dans la sous-section suivante.

Il est important de noter que la boucle while est une boucle purement impérative, puisqu'elle travaille sur le changement de valeur d'une variable. En programmation fonctionelle, la valeur d'une variable ne peut changer, la boucle while n'a donc plus aucune utilité !

La boucle for

En Scala, la boucle for utilise la syntaxe suivante (l'exemple est le même que précédemment) :

for(i <- 0 to 9)
	println(i)

Voici les détails du morceau de code ci-dessus :

Manipulation des listes

Les listes sont un type de variable très utilisé en Scala, et plus généralement en programmation fonctionnelle. Cette section a pour objectif d'expliquer brievement les principaux outils proposés par Scala pour les manipuler.

En Scala, la création d'une nouvelle liste se fait au travers d'une factory présente dans le companion object de la classe List. Le mot-clé «new» ne doit donc pas être présent lors de l'instanciation d'une nouvelle liste, bien que cela puisse paraître étrange au premier abord. Puisque les listes sont non-mutables en Scala, la liste vide est instanciée systèmatiquement et sa référence sera renvoyée pour toute instanciation de liste vide dans le programme. Cette instance de liste vide a un nom : «Nil». L'exemple suivant applique ces notions d'instanciation de listes :

 
scala> val l1 : List[Int] = List()
l1: List[Int] = List()

scala> val l2 : List[Int] = Nil
l2: List[Int] = List()

scala> val l3 = List(42, 666)
l3: List[Int] = List(42, 666)

Le langage Scala propose les deux mots-clés «::» et «:::» pour manipuler les listes :

Il est important de rappeler que les listes sont non-mutables, ainsi ces deux opérateurs renvoient systématiquent une nouvelle liste sur laquelle les changements ont été appliqués. Voici un exemple d'utilisation de ces deux opérateurs : une liste vide se voit d'abord concaténer avec un élément, puis avec une liste :

scala> val l1 = Nil
l1: scala.collection.immutable.Nil.type = List()

scala> val l2 = 1 :: l1
l2: List[Int] = List(1)

scala> val l3 = l2 ::: List(2, 3, 4)
l3: List[Int] = List(1, 2, 3, 4)

Conclusion

Scala est bien un langage objet-fonctionnel-impératif :