Feuille 1 — le langage Scala
Paradigme fonctionnel, types & déclarations, fonctions, POO (classes / case class / traits) et pattern matching. Tout ce qu'il faut pour lire et écrire du Scala.
1. Paradigme fonctionnel
Fonctionnel ≡ pas d'effet de bord : une fonction ne lit que ses paramètres et ne modifie rien d'extérieur. 5 substitutions impératif → fonctionnel :
| variable | → | constante (val) |
| instruction | → | expression typée |
| procédure | → | fonction pure |
| boucle | → | récursivité |
Pure = déterministe (même entrée → même sortie) + aucun effet de bord (pas de println, Random, mutation, I/O). C'est une constante typée.
Récursion terminale (l'appel récursif est la dernière opération) → pas d'empilement, pas de StackOverflow :
// simple : empile n cadres
def fact(n:Int):Int =
if (n<=0) 1 else n*fact(n-1)
// terminale : 1 cadre (accumulateur)
def factTR(n:Int, acc:Int):Int =
if (n==0) acc else factTR(n-1, n*acc)
2. Déclarations & types
val x = 7 // immuable (≡ final), type inféré Int
var y:String = "hi" // mutable — préférer val
x = 8 // ✗ erreur COMPILE-TIME (réassign. d'un val)
Division entière (piège) : convertir avant.
1 / 2 // 0 (Int)
(1/2).toDouble // 0.0 — trop tard !
1.toDouble / 2 // 0.5 ✓
lazy val : évalué au 1ᵉʳ usage réel, puis figé (1 seule fois). Tout est expression : if renvoie une valeur ; son type = supertype commun des branches. Un bloc vaut sa dernière expression et crée un scope local.
if (true) 42 else "42" // : Any
if (true) 42 else 42.0 // : Double
val e = { val k=2.54; 15*k } // 38.1 ; k invisible dehors
println(1) // : Unit = ()
Any au sommet, Nothing sous-type de tout, Null sous les AnyRef. Unit ≡ void.
3. Tuples & Option[T]
val t = ("a", 42, 3.0) // (String,Int,Double), max 22
t._2 // 42 — accès 1-indexé (._1 ._2 …)
val (a,b,c) = t // déstructuration
Option[T] = Some(x) | None : remplace les exceptions, l'absence devient normale.
def toInt(s:String):Option[Int] =
try Some(s.trim.toInt)
catch { case _:Exception => None }
toInt("42") // Some(42)
toInt("x") // None
4. Fonctions
def f(a:Int, b:Int = 2):Int = a+b // type param obligatoire
println(s"1+1=${1+1}") // interpolation s"…"
by value (défaut) : évalué à l'appel, 1×. by name => T : évalué à chaque usage (0, 1 ou N fois) → court-circuit, paresse.
def et(x:Boolean, y: => Boolean) = if (x) y else false
et(false, 1/0==0) // false (1/0 jamais évalué)
Lambda : chaque _ = un nouveau paramètre. @tailrec force la récursion terminale.
val inc: Int=>Int = _ + 1
val add: (Int,Int)=>Int = _ + _ // 2 params
val sq: Int=>Int = x => x*x // ✗ pas _*_ (=2 params)
HOF / currying / application partielle :
def applyTwice(f:Int=>Int, x:Int) = f(f(x))
applyTwice(_+1, 2) // 4
def twice(f:Int=>Int)(x:Int) = f(f(x)) // curry
twice(_+1)(2) // 4
val g = transv(_:Int, 1, _:Int) // partielle (types !)
def = méthode (eta-expansion f _ pour l'utiliser comme valeur) · val = objet fonction avec .apply : f(x) ≡ f.apply(x). Un val récursif exige un type de retour explicite ; un val ne peut pas être générique.
5. POO — classes & objets
class A(x:Int, val y:Int, var z:Int) {
def this(x:Int) = this(x, 0, 0) // constructeur auxiliaire
}
new A(1,2,3).y // 2
| param classe | visibilité | mutabilité |
|---|---|---|
| (rien) | privé | immuable |
val | public | immuable |
var | public | mutable |
object = singleton lazy (pas de [T] ni params) ≈ static. Companion = object de même nom qu'une classe (accès privé mutuel) → factory via apply. Méthode infixe : a op b ≡ a.op(b) (1 paramètre).
6. case class & ADT
case class = 4 cadeaux : ① params val publics ② == compare le contenu ③ toString lisible ④ companion apply (pas de new) + unapply (matching) + copy.
case class Person(last:String, first:String)
val p = Person("Dujol","Romain") // pas de new
p == Person("Dujol","Romain") // true (contenu)
p.copy(first="R.") // clone modifié
sealed + case = ADT (type somme) → exhaustivité vérifiée à la compilation. final = 0 sous-type ; sealed = sous-types dans le même fichier.
sealed abstract class Expr
case class Num(v:Int) extends Expr
case class Add(l:Expr, r:Expr) extends Expr
def eval(e:Expr):Int = e match {
case Num(v) => v
case Add(l,r) => eval(l)+eval(r) // récursion = structure
}
La récursion structurelle suit exactement les case du sealed.
7. traits & génériques
trait = interface + val/var + implémentations partielles, mixable en nombre illimité. extends = 1ʳᵉ (classe ou trait), with = traits suivants. Au plus une classe étendue.
trait Ordered2[T] { // "Scala style" : minimal
def <(t:T): Boolean
def >(t:T) = !(this<t) && this!=t // dérivé
}
class C extends A with T1 with T2
val r = P("D","R") with Emp { override val dept="Maths" }
Bornes : [T <: U] upper (T étend U) · [T >: L] lower. Variance : +T covariant (résultat) · -T contravariant (entrée) · défaut invariant. Ex : Function1[-T,+R], Option[+T].
8. Pattern matching
x match {
case Person("Dujol", _) => "famille" // littéral + _
case Person(l, f) if l==f => "jumeau" // garde if
case (a, b) => a+b // déconstruction
case _ => "autre" // catch-all
}
_ capture le reste. Avec sealed, le compilo vérifie.1ᵉʳ
case qui matche gagne (sinon unreachable code).un identifiant 1 seule fois :
case (x,x) ✗ → case (x,y) if x==y.Le résultat du match est une expression (type = supertype commun des cas). Omniprésent : List (head :: tail), case classes, messages Akka.