Le carnet On est
Anti-sèche · à recopier à la main

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 :

variableconstante (val)
instructionexpression typée
procédurefonction pure
boucleré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)
simple ↗ (pile monte) ⚠ StackOverflow terminale → (plate) 1 seul cadre ≡ boucle (TCO)

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 AnyVal AnyRef Int Double Boolean Char Unit … String List Seq (tes classes) Null Nothing

Any au sommet, Nothing sous-type de tout, Null sous les AnyRef. Unitvoid.

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 classevisibilitémutabilité
(rien)privéimmuable
valpublicimmuable
varpublicmutable

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
}
① Exhaustivité
_ capture le reste. Avec sealed, le compilo vérifie.
② Séquentialité
1ᵉʳ case qui matche gagne (sinon unreachable code).
③ Unicité
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.