Logo Blent.ai
← Retourner à la liste des articles
Image blog
Auteur

Par Maxime Jumelle

CTO & Co-Founder

Publié le 28 févr. 2022

Catégorie Data Engineering

Scala : tout savoir sur le langage fonctionnel

Scala tire son nom de « scalable language » car il a été pensé pour être utilisé à n'importe quelle échelle : de simples scripts jusqu'à de larges systèmes. Développé initialement en 2004 à l'EPFL, Scala s'intègre complètement avec Java et s'exécute en tant que plateforme Java.

Aujourd'hui, le langage Scala est principalement utilisé dans les applications Big Data, car elles sont historiquement développées en Java, mais ce dernier est quelques fois trop lourd au niveau de la syntaxe et manque surtout d'un paradigme très utile pour des opérations de calculs distribués : la programmation fonctionnelle.

Logo Scala

Les deux paradigmes du langage Scala

On considère souvent que Scala est un langage difficile. En principe, tous les langages sont difficiles : en maîtriser un signifie bien plus que connaître par cœur la syntaxe. Dans les faits, Scala peut sembler déroutant aux premiers abords car il mélange deux paradigmes.

  • La programmation orienté objet, devenue un standard dans le développement et très présent en Java, C# ou C++, où l'on construit des objets et des relations entre ces derniers.
  • La programmation fonctionnelle, qui puise ses origines du λ\lambda-calcul, où tout est considéré comme étant des fonctions mathématiques sur des structures algébriques (popularisé à ses débuts par le langage Haskell).

Et c'est en partie à cause de ce double paradigme que les nouveaux utilisateurs de Scala peuvent avoir des difficultés.

Les développeurs Java qui, par exemple, sont très attachés à l'orienté objet et à l'impératif (boucle for), ont beaucoup de difficultés à utiliser l'approche fonctionnelle, car ce n'est pas dans leurs habitudes. De plus, il y a beaucoup de sucre syntaxique dans Scala, c'est-à-dire de formatage de code plus digeste et plus abrégé, en comparaison avec Java qui est beaucoup plus verbeux.


À lire aussi : découvrez notre formation Data Engineer


Les développeurs Python ou R (Data Scientist par exemple) ont déjà en tête l'utilisation de certains paradigmes fonctionnels (apply sous pandas ou avec dplyr), et la syntaxe de Scala leur est beaucoup plus familière. En revanche, ces deux langages n'intègrent que peu d'orienté objet et le typage est plus dynamique, ce qui perturbe également ces développeurs.

Paradigme fonctionnel

La programmation fonctionnelle est un des piliers du langage Scala. C'est sa principale différence avec le Java et bien qu'il permette de faire du fonctionnel, cela nécessite beaucoup plus de code avec une syntaxe pas toujours adaptée. De plus en plus de frameworks tendent vers cette approche fonctionnelle, car elle possède de nombreux avantages.

  • Elle permet de définir un cadre mathématique rigoureux.
  • Elle est adaptée dans les situations de calculs distribués.
  • Elle offre de nombreuses possibilités pour le sucre syntaxique.

Certains frameworks comme Spark, Akka ou encore Flink utilisent beaucoup cette approche fonctionnelle (tout en n'oubliant pas le paradigme orienté objet), où les fonctions en forment la composante principale.

Paradigme objet

L'orienté objet est un paradigme de programmation où l'on définit et instancie des objets. Ces objets peuvent être de différentes natures, comme les classes, les interfaces ou les modules. Le langage Scala requiert l'utilisation de l'orienté objet en coordination avec le paradigme fonctionnel : cela nécessite l'utilisation de certaines notions déjà présentes en Java comme les types génériques, mais on retrouve également de nouveaux concepts comme les cases classes, les objets compagnons ou les contraintes de types.

Structures et types

Sous Scala, il existe de nombreuses implémentations de structures de collections. Elles sont bien évidemment indispensables dans la plupart des cas, et l'on retrouve les structures classiques.

  • Les séquences, où les éléments sont ordonnés par leur position : on retrouve par exemple les List, les Stack ou encore les Vector.
  • Les ensembles, dont les ListSet ou les HashSet.
  • Les dictionnaires, où les éléments sont représentés par des couples clés/valeurs, tels que les ListMap ou TreeMap.

Ces collections sont par défaut immuables : cela signifie que l'on ne peut pas modifier leurs instances. Par exemple, dans une liste immuable, il n'est pas possible d'ajouter ou de modifier les éléments. Il faudrait dans ce cas créer une nouvelle variable ou utiliser une liste mutable.

1// Manipulation de listes en Scala
2val maListe = List(1, 2, 3, 4)
3println(maListe.head) // 1
4println(maListe.tail) // List(2, 3, 4)
5println(0 +: maListe) // List(0, 1, 2, 3, 4)
6println(0 :: maListe) // List(0, 1, 2, 3, 4) = Équivalent à l'opérateur précédent
7println(maListe :+ 5) // List(1, 2, 3, 4, 5)
8println(List(-1, 0) ::: maListe) // List(-1, 0, 1, 2, 3, 4)
9println(maListe ::: List(5, 6)) // List(1, 2, 3, 4, 5)

Le choix de la bonne structure est essentiel pour chaque situation. En effet, sous Scala, on s'intéresse beaucoup à la notion de complexité de calcul, et on cherche toujours à proposer des algorithmes les plus rapides.

L'exemple le plus primordial concerne les listes. La représentation d'une liste en Scala est celle d'une liste chaînée : ajouter un élément en début de liste est quasi-immédiat (complexité O(1)O(1)), alors que rajouter un élément en fin de liste suppose de parcourir toute cette liste (complexité O(n)O(n) avec nn la taille de la liste), ce qui est beaucoup moins optimisé.

1// Concaténation à droite
2val grandeListe = (1 to 100000).toList
3var timerDroite = System.nanoTime()
4grandeListe :+ 0
5var dureeDroite = (System.nanoTime() - timerDroite) / 10e9
6println(f"Durée de la concaténation à droite : $dureeDroite")
Durée de la concaténation à droite : 6.679373E-4

Et maintenant, pour une concaténation à gauche.

1// Concaténation à gauche
2var timerGauche = System.nanoTime()
30 +: grandeListe
4var dureeGauche = (System.nanoTime() - timerGauche) / 10e9
5println(f"Durée de la concaténation à gauche : $dureeGauche")
6println(f"La concaténation à droite est ${dureeDroite / dureeGauche}%.2fx plus lente.")
Durée de la concaténation à gauche : 7.079E-7
La concaténation à droite est 943,55x plus lente.

Opérations HOF

Les Higher-Order Functions (HOF) sont très importantes en programmation fonctionnelle. En effet, ce sont elles qui permettent d'implémenter du λ\lambda-calcul programmable. Une HOF est une fonction FF qui prend en arguments au moins une fonction et retourne une fonction comme résultat.

Parmi les HOF les plus populaires, on retrouve notamment la fonction map. La fonction map sur une collection [x1,,xn][x_1, \dots, x_n] applique une fonction ff sur chaque élément de la collection et retourne le même type de collection [f(x1),,f(xn)][f(x_1), \dots, f(x_n)].

map(f)([x1,,xn])=[f(x1),,f(xn)]\text{map}(f)([x_1, \dots, x_n])=[f(x_1), \dots, f(x_n)]

Par exemple, supposons avoir la liste suivante.

1val maListFor = List(1, 2, 3, 4)

Et que l'on souhaite appliquer un traitement à chaque élément de cette liste, et retourner ces résultats dans une liste. Plutôt que d'utiliser une boucle for (déconseillée en Scala), on peut utiliser la fonction map.

1println { maListFor.map(_ * 2) }
2println {
3  maListFor.map(x => {
4    if (x <= 2) x
5    else x * 2
6  })
7}
8println { maListFor.map(x => List(x, x * 2)) }
List(2, 4, 6, 8)
List(1, 2, 6, 8)
List(List(1, 2), List(2, 4), List(3, 6), List(4, 8))

Il existe plusieurs opérations HOF (flatMap, reduce, fold, ...), et elles sont fondamentales dans l'approche fonctionnelle.

Objets et classes

Les classes constituent la base de l'orienté objet. Une classe est un objet qui contient plusieurs champs/propriétés (variables, méthodes, constructeur) et qui peut être instancié en tant que variable.

Contrairement au Python, on utilise beaucoup d'objets et de classes dans les projets et les scripts Scala. Il est donc courant de créer des classes de petites tailles pour des besoins précis ou d'hériter depuis des classes de frameworks existant pour adapter son code à son projet.

Une première classe

Modélisons une première classe qui représente un ordinateur que nous allons nommer Computer.

  • Chaque ordinateur doit disposer des variables brand (immuable) qui indique la marque de l'ordinateur et os (mutable) qui indique le système d'exploitation.
  • Seules deux méthodes seront présentes : start et stop, qui vont toutes les deux agir sur une variable isOn indiquant l'état de l'ordinateur (allumé/éteint).
1class Computer(val brand: String, var os: String) {
2  var isOn = false // L'ordinateur est allumé ?
3  def start(): Unit = this.isOn = true
4  def stop(): Unit = this.isOn = false
5}

Ce qui est particulier ici, c'est que le constructeur (méthode d'initialisation) se définit directement après le nom de la classe, là où en Java, il faudrait créer un constructeur spécifiquement.

1// Code Java
2class Computer {
3
4  Computer(String os, String brand) {
5    // ...
6  }
7
8  // ...
9}

Héritage

L'héritage est un concept qui permet de dériver une classe en une autre plus spécifique tout en conservant les propriétés déjà présentes.

Pour cela, on utilise le mot réservé extends.

1class Laptop(brand: String, os: String) extends Computer(brand, os) {
2  var isOpen = false
3  def open(): Unit = isOpen = true
4  def close(): Unit = {
5    if (isOn) super.stop()  // Fermer le portable le stoppe automatiquement (super = insiste bien pour appeler une fonction de la classe parente)
6    isOpen = false
7  }
8  // Les fonctions start et stop sont déjà présentes
9}
10
11val laptop = new Laptop("Dell", "Windows")
12laptop.start()
13println(f"Allumé : ${laptop.isOn}")
14laptop.close() // Appelle la fonction stop() puisque le portable est allumé
15println(f"Allumé : ${laptop.isOn}")
Allumé : true
Allumé : false

Le fait de pouvoir utiliser une approche fonctionnelle sur des objets rend le langage Scala extrêmement puissant, à condition d'utiliser correctement et à bon escient les modèles de chaque paradigme.

Quel environnement pour Scala ?

Tout comme la plupart des langages, il est fortement conseillé d'utiliser un IDE. IntelliJ IDEA (dans sa version Community) est sûrement le plus populaire pour coder en Scala, et il dispose de plugins Scala qui permettent de récupérer les sources des différentes versions et de développer très rapidement un projet.


À lire aussi : découvrez notre formation Data Engineer


Il est aussi possible d'utiliser des outils comme sbt (Scala Build Tool) pour développer et package des projets Scala que nous n'aborderons pas dans ce cours.

Puisque Scala s'exécute sur la JVM (Java Virtual Machine), il suffit juste d'avoir une version de développement Java installé sur son PC (Windows, Linux ou Mac) pour commencer directement à coder en Scala. Il n'est pas nécessaire d'avoir une machine puissante, car Scala hérite de la légèreté d'exécution de Java.

Comment se former en Scala ?

La maîtrise du langage Scala demande un investissement en temps, car pour manipuler ce langage, il est nécessaire de comprendre toutes ses spécificités.

Blent propose une formation complète en Scala afin de pouvoir maîtriser les deux paradigmes fonctionnels et objets de ce langage. De nombreux exercices viennent compléter l'apprentissage tout au long du parcours de formation.

Articles similaires

Blog

23 mai 2023

Data Engineering

Apache Phoenix est une extension open-source de Apache HBase qui fournit une couche de requêtes SQL pour les données stockées dans HBase. Phoenix permet ainsi d'interagir sur les tables HBase à l'aide de requêtes SQL standard, sans avoir à écrire de code spécifique à HBase
Maxime Jumelle

Maxime Jumelle

CTO & Co-Founder

Lire l'article

Blog

15 mai 2023

Data Engineering

Apache Avro est un système de sérialisation de données et un format de données compact, rapide et polyvalent. Il a été développé par Apache Software Foundation et est conçu pour faciliter l'échange de données entre les différentes applications. Contrairement à d'autres formats comme CSV ou JSON, une des grandes particularités d'Apache Avro est qu'il utilise un schéma pour définir la structure des données, ce qui permet de sérialiser et de désérialiser les données de manière efficace, tout en garantissant la compatibilité entre les différentes versions des schémas.
Maxime Jumelle

Maxime Jumelle

CTO & Co-Founder

Lire l'article

Blog

12 mai 2023

Data Engineering

Apache Flink est un système de traitement des données en temps réel et de traitement par lots à grande échelle. Il s'agit d'un projet open source développé par la fondation Apache, conçu pour offrir une haute disponibilité, une faible latence et une grande capacité de traitement des flux de données. Flink fournit un modèle algorithmique unifié qui permet de traiter les données en temps réel et par lots de manière cohérente. Il prend en charge des opérations avancées telles que la transformation, le filtrage, l'agrégation et la jointure de données en continu.
Maxime Jumelle

Maxime Jumelle

CTO & Co-Founder

Lire l'article

Logo Blent

60 rue François 1er

75008 Paris

Blent est une plateforme 100% en ligne pour se former aux métiers Tech & Data.

Organisme de formation n°11755985075.

Gestion des cookies

© 2023 Blent.ai | Tous droits réservés