c'est simple pour les développeurs Java Pascal Wassong Une classe Scala package com.example.scala import scala.annotation.tailrec class Rationel(num: Int, denom: Int) { def this(n: Int) = this(n, 1) private val p = pgcd(num, denom) val numerateur = num / p val denominateur = denom / p lazy val reel = numerateur.toFloat / denominateur println("valeur réelle = " + reel) override def toString = numerateur.toString + " / " + denominateur.toString @tailrec private def pgcd(a: Int, b: Int): Int = if (b == 0) a else pgcd(b, a % b) } Moins de limitations qu'en Java ● Scala a un « interpréteur » (REPL) la commande : scala ● ● Pas de limite au nombre de classes par fichier Une classe peut être définie à l'intérieur d'une autre ● Package comme en Java ● Pas de contraintes sur l'emplacement d'un fichier Une classe ● Déclarer une classe class Rationel(num: Int, denom: Int) { … } ● Constructeur primaire et secondaire ● « extends » une seule autre classe ● Utilisation de traits avec le mot clé « with » class MaClasse extends AutreClasse with TraitA with TraitB { … } ● Classe abstraite comme en Java : abstract Import ● Importer une classe comme en Java ● La clause import peut être utilisée n'importe où ● Importer toutes les classes d'un package import java.util._ ● Importer plusieurs classes d'un package import java.util.{LinkedList, HashMap} ● Renommer une classe importée import java.util.Date import java.sql.{Date => SqlDate} class MaClasse { val date: Date = new Date() val sqlDate: SqlDate = new SqlDate(date.getTime()) } Une classe Scala package com.example.scala import scala.annotation.tailrec class Rationel(num: Int, denom: Int) { def this(n: Int) = this(n, 1) private val p = pgcd(num, denom) val numerateur = num / p val denominateur = denom / p lazy val reel = numerateur.toFloat / denominateur println("valeur réelle = " + reel) override def toString = numerateur.toString + " / " + denominateur.toString @tailrec private def pgcd(a: Int, b: Int): Int = if (b == 0) a else pgcd(b, a % b) } Un trait trait Compteur { def maxAutorise: Int var compteur = 0 def incremente(): Unit = compteur += 1 } ● Un trait est comme une interface Java, en mieux. ● Peut définir des méthodes, des var et des val. ● Peut contenir du code, et donner des valeurs aux val et var. ● Pas de constructeur, donc pas d'arguments. ● Peux étendre un autre trait avec extends. Quelques mots réservés ● var, def ● val : équivalent de l'ajout de « final » en Java ● ● ● private, protected, public est par défaut. Le mot clé n'existe pas. override est un mot clé lazy val : la valeur n'est initialisée qu'au 1 er accès Inférence de type ● ● Souvent, le compilateur a les moyens de trouver le type d'une expression Par exemple val i = 1 <=> val s = "une chaîne" val i: Int = 1 <=> val s: String = "une chaîne" def pair(n: Int) = if (n % 2 == 0) "pair" else "impair" def pair(n: Int): String = if (n % 2 == 0) "pair" else "impair" ● ● Conseillé pour le type de retour de toutes les méthodes Obligatoire pour les méthodes récursives Une classe Scala package com.example.scala import scala.annotation.tailrec class Rationel(num: Int, denom: Int) { def this(n: Int) = this(n, 1) private val p = pgcd(num, denom) val numerateur = num / p val denominateur = denom / p lazy val reel = numerateur.toFloat / denominateur println("valeur réelle = " + reel) override def toString = numerateur.toString + " / " + denominateur.toString @tailrec private def pgcd(a: Int, b: Int): Int = if (b == 0) a else pgcd(b, a % b) } Les choses optionnelles override def toString = numerateur.toString + " / " + denominateur.toString override def toString(): String = { return numerateur.toString() + " / " + denominateur.toSring(); } ● ; ● () : si pas d'argument => transparence référentielle ● {} : si une seule expression ● Le type de retour : trouvé par inférence ● return : tout est expression object object Rationel { def apply(n: Int, d: Int) = new Rationel(n, d) def apply(n: Int) = new Rationel(n, 1) val UN = Rationel(1) } ● Singleton ● Méthodes « static » ● Objet compagnon ● La méthode magique « apply » Tout est objet, tout est expression ● Tout est objet, les méthodes aussi 2.to(10) ou bien "314".toInt def double(i: Int): Int = i * 2 def calculer(i: Int, fct: (Int) => Int): Int = fct(i) + i calculer(3, double) + calculer(3, i => i * i) ● Tout est une expression val s = "314" val i = try { s.toInt } catch { case e: java.lang.NumberFormatException => 0 } val res = if (i % 2 == 0) i / 2 else 3 * i + 1 ● Unit est l'équivalent de « void » en Java Domain Specific Language ● ● ● Dans l'appel d'une fonction, le point « . » est optionnel. Une fonction avec un seul argument peut être invoquée sans écrire les parenthèses Deux exemples 2.to(10) <=> 2 to 10 2 + i ● <=> 2.+(i) Surcharge des opérateurs (ne pas abuser) – Tous les caractères peuvent être utilisés dans les noms des méthodes. Par exemple : def \:/ = "victoire" Exemple de DSL : ScalaTest import collection.mutable.Stack import org.scalatest._ class ExampleSpec extends FlatSpec with Matchers { "A Stack" should "pop values in last­in­first­out order" in { val stack = new Stack[Int] stack.push(1) stack.push(2) stack.pop() should be (2) stack.pop() should be (1) } it should "throw NoSuchElementException if an empty stack is popped" in { val emptyStack = new Stack[Int] a [NoSuchElementException] should be thrownBy { emptyStack.pop() } } } Tuple (n-uplet) ● Un tuple permet de rassembler plusieurs valeurs def bornes(liste: List[Int]): (Int, Int) = { val min = liste.min val max = liste.max (min, max) } val liste = List(2, 3, 5, 7, 11, 13) val minMax = bornes(liste) println( s"min = ${minMax._1}, max = ${minMax._2}" ) ● Mieux val (min, max) = bornes(liste) println( s"min = $min, max = $max" ) ● Et encore mieux … case class case class Bornes(min: Int, max: Int) val b = Bornes(2, 13) println(b.toString) ● Création d'une classe complète en une seule ligne ● Ce sont des val par défaut ● Accès aux valeurs ● toString, equals, hashCode ● Objet compagnon avec la fabrique apply Pattern matching def texte(liste: List[Int]): String = liste match { case Nil => "La liste est vide" case List(x) => "La liste contient un seul element : " + x case x :: xs if x == 0 => "La liste commence par 0" case _ => "Tous les autres cas" } ● Mille fois plus puissant qu'un switch ● Déconstruction : les variables x et xs contiennent les valeurs ● if est une clause de garde ● :: est l'opérateur définit sur List pour ajouter un élément en tête de liste Les boucles ● while var i = 0 while (i < 10) { println(s"i = $i") i = i + 1 } ● for for (i <­ 0 to 9) println(s"i = $i") ● foreach (0 to 9).foreach(i => println(i)) val liste = List(2, 3, 5, 7, 11, 13) liste.foreach(n => println(n)) ● zipWithIndex liste.zipWithIndex.foreach { case (n, i) => println(s"$i: $n") } Programmation fonctionnelle ● Un exemple val liste = List(2, 3, 5, 7, 11, 13) liste.filter(_ > 10).map(_ * 2).foreach(println) ● ● Les collections sont immutables par défaut. Le type Option[T], pour éviter l'utilisation de null. L’écosystème Scala ● ● Livre : « Programming in Scala » de Odersky, Spoon et Venners : http://www.artima.com/pins1ed Scaladoc : http://www.scala-lang.org/api/current/index.html ● Librairie akka (acteur) avec interface Java ● Frameworks Web en Scala : Play, spray, Lift ● IDE : plugins pour IntelliJ et Eclipse (ensime pour emacs) ● Utilisateurs de Scala : Twitter, LinkedIn, TheGuardian, etc … ● Introduction de Scala dans les tests unitaires Merci pour votre attention [email protected] Ce document est Copyright © Pascal Wassong 2016, Creative Commons : CC-BY-ND-SA L'image page 2 est © Tomi Ungerer