jeudi 4 novembre 2010

Pourquoi Scala? (Introduction)

Vous entendez parler tous les jours de Scala, tous vos collègues vous disent que le langage Java est devenu sénile (surtout depuis que les modifications les plus intéressantes ont été reportées au jdk8) ? Vous ne savez pas à quoi peut bien servir ce fichu Scala et vous en avez marre de vous sentir idiot lors des discussions devant la machine à café? Cette petite introduction au langage est faite pour vous!

Scala est un langage de programmation statiquement typé, orienté objet et fonctionnel. Comme en Java on utilise le paradigme objet pour modéliser une application et les entités dont elle est composée, mais on a également la possibilité d'implémenter les méthodes de nos classes en utilisant une approche fonctionnelle.
Il est important de savoir que le code écrit en Scala est compilé en bytecode Java afin d'être exécuté sur une JVM. Ceci est une bonne nouvelle pour nous développeurs Java, puisqu'on ne repart pas de zéro sur une nouvelle plateforme : on pourra profiter de toute la richesse des bibliothèques Java, elles seront directement utilisables dans nos projets Scala.

Pour trois petits problèmes simples de programmation, nous allons voir une solution classique en Java et comment Scala pourrait nous simplifier la tache.

Problème 1
J'ai un simple objet avec des attributs x et y. J'ai besoin d'avoir une encapsulation des champs car je veux exposer ma classe à travers une API, sans avoir à casser la compatibilité le jour où je voudrai renvoyer x+1 au lieu de x. Je veux aussi un constructeur utilisant ces champs.

Solution Java
J'encapsule mes champs avec des getters et setters, je crée un constructeur.
public class Rectange{

private int largeur;
private int longeur;

public Rectangle(int largeur, int longueur){
this.largeur=largeur;
this.longeur=longeur;
}

public int getLargeur(){
return largeur;
}

public void setLargeur(int largeur){
this.largeur=largeur;
}

public int getLongeur(){
return longeur;
}

public void setLongeur(int longeur){
this.longeur=longeur;
}

}

On constate qu'on a écrit beaucoup de code pour pas grand chose.... Les comportements de ces getters/setters et du constructeur pourraient être induits à partir d'un fonctionnement par défaut.

C'est cette approche qui est privilégiée dans la solution Scala :

class Rectangle(var largeur: Int, var longueur: Int)
C'est tout! Si on a besoin de redéfinir un getter par la suite on pourra ajouter une méthode sans casser la compatibilité avec les classes appelantes (voir liens en bas du post si vous voulez rentrer dans les détails).

Problème 2
Je veux créer une liste d'entiers et la filtrer pour récupérer uniquement les entiers positifs.

Solution Java
List<Integer> liste = Arrays.asList(-1,2,-3,4,-5,6);
List<Integer> positifs = new ArrayList<Integer>();
for (Integer x : liste){
 if(x > 0)
  positifs.add(x) 
}


Solution Scala
val liste = List(-1,2,-3,4,-5,6)
val positifs = liste.filter(x=>x>0)
On peut aussi simplifier l'écriture comme ceci avec le caractère joker '_' :
val positifs = liste.filter(_>0)
Enfin, on peut chainer les appels de fonctions. Par exemple, pour afficher nos entiers positifs incrementés de 1 :
liste.filter(_>0).map(_+1).println(_)
On voit bien là la puissance de l'approche fonctionelle de Scala. On passe une fonction anonyme (lambda) à la méthode filter (fournie par l'API Scala) pour retourner une sous partie de notre liste.
Il est possible de définir des prédicats (ou d'utiliser Guava) en Java pour se rapprocher d'une approche fonctionnelle, mais tant que les fonctions lambda seront absentes du langage, on ne pourra pas obtenir une syntaxe aussi élégante.

Problème 3
Je veux ouvrir et parcourir un fichier

Solution Java
String filePath = "fichier.txt";

try{
BufferedReader buff = new BufferedReader(new FileReader(filePath));
try {
List<String> lines = new ArrayList<String>();
while ((line = buff.readLine()) != null) {
lines.add(line);
}
} finally {
buff.close();
}
} catch (IOException ioe) {
System.out.println(ioe.toString());
}
Un peu lourd et surtout pas très lisible (ah les checked exceptions de Java...) !

Solution Scala
val lines = Source.fromFile("fichier.txt").mkString
Ça se passe de commentaires...

Problème 4
Je veux écrire un singleton et y accéder

Solution Java
public class MonSingleton {
    
    private MonSingleton () {
    }
 
    public static MonSingleton getInstance() {
        if (null == instance) {
            instance = new MonSingleton();
        }
        return instance;
    }

    private static MonSingleton instance;
 
 public void hello(){
System.out.println("hello");
}
}
Pour arriver à mes fins j'ai rendu le constructeur par défaut privé, créé une instance statique et une méthode pour y accéder.

Solution Scala
object MonSingleton {
def hello() = {
  println("hello")
}
}

La notion d'objet en Scala permet de définir une instance unique de classe, donc un singleton. A chaque fois que j’accéderai à mon objet en appelant MonSingleton.hello, ce sera la même instance qui sera utilisée.

Aller plus loin
Pour tester les possibilités du langage vous pouvez utiliser la console Scala qui est livrée avec le SDK. Vous pouvez par exemple écrire votre première fonction prenant en paramètre une autre fonction :
Définition d'une première fonction simple
scala> (x: Int) => x + 1
res1: Int => Int = <function1>

Définition d'une fonction prenant un entier et une fonction en paramètre
scala> (x:Int, f:(Int)=>Int) => f(f(x))
res2: (Int, Int => Int) => Int = <function2>

On peut maintenant l'utiliser comme ceci :
scala> res2(3,res1)

Autre concept utile, le pattern matching est un mécanisme qui permet de gérer finement des conditions sur des valeurs. On peut tester le contenu d'un objet ou son type et définir des actions associées à chaque cas :
  def chanteur(x: String): String = x match {
    case "Metallica" => "James Hetfield"
    case "Iron Maiden" => "Bruce Dickinson"
    case _ => "I don't know"
  }

  def quelType(x: Any) : String = x match{
    case _ : String => "Chaine"
    case _ : Int | _ : Long => "Entier"
    case _ => "I don't know"
  }
Le '_' permet de définir le cas "joker".
Il existe aussi une notion appelée case class qui permet de déterminer des actions en fonction de valeurs d'instances pour une classe.

Tout ceci n'était qu'une mise en bouche, le langage offre beaucoup d'autres possibilités notamment par son approche fonctionnelle.

Je vous suggère de regarder ces articles :