
Scala est un langage de programmation de haut niveau à usage général qui offre un équilibre entre le développement de programmes fonctionnels et orientés objet.
Qu'est-ce que la programmation fonctionnelle? En termes simples, les fonctions sont les citoyens de première classe de la programmation fonctionnelle. Afin de s'étendre sur un ensemble de fonctionnalités de base d'un programme, nous avons tendance à écrire des classes supplémentaires s'étendant sur certaines lignes directrices / interfaces. En programmation fonctionnelle, les fonctions nous aident à réaliser la même chose.
Nous utiliserons le Scala REPL pour toutes les explications. C'est un outil très pratique et informatif pour apprendre Scala. Il enregistre de jolis petits messages sur la façon dont notre code est interprété et exécuté.
Commençons par les bases d'abord.
1. Variables
Nous pouvons définir des variables immuables en utilisant val
:
scala> val name = "King"name: String = King
Les variables mutables peuvent être définies et modifiées en utilisant var
:
scala> var name = "King"name: String = King
scala> name = "Arthur"name: String = Arthur
Nous utilisons def
pour attribuer une étiquette à une valeur immuable dont l'évaluation est différée à une date ultérieure. Cela signifie que la valeur de l'étiquette est évaluée paresseusement à chaque utilisation.
scala> var name = "King"name: String = King
scala> def alias = namealias: String
scala> aliasres2: String = King
Avez-vous observé quelque chose d'intéressant?
Lors de la définition alias
, aucune valeur n'a été assignée alias: String
car elle est associée paresseusement, lorsque nous l'appelons. Que se passerait-il si nous changions la valeur de name
?
scala> aliasres5: String = King
scala> name = "Arthur, King Arthur"name: String = Arthur, King Arthur
scala> aliasres6: String = Arthur, King Arthur
2. Flux de contrôle
Nous utilisons des instructions de flux de contrôle pour exprimer notre logique de décision.
Vous pouvez écrire une if-else
déclaration comme ci-dessous:
if(name.contains("Arthur")) { print("Entombed sword")} else { print("You're not entitled to this sword")}
Ou, vous pouvez utiliser while
:
var attempts = 0while (attempts < 3) { drawSword() attempts += 1}
3. Collections
Scala fait une distinction explicite entre les collections immuables et mutables - directement depuis l'espace de noms du package lui-même ( scala.collection.immutable
ou scala.collection.mutable
).
Contrairement aux collections immuables, les collections mutables peuvent être mises à jour ou étendues sur place. Cela nous permet de modifier, d'ajouter ou de supprimer des éléments en tant qu'effet secondaire.
Mais l'exécution d'opérations d'ajout, de suppression ou de mise à jour sur des collections immuables renvoie une nouvelle collection à la place.
Les collections immuables sont toujours automatiquement importées via le scala._
(qui contient également un alias pour scala.collection.immutable.List
).
Cependant, pour utiliser des collections mutables, vous devez importer explicitement scala.collection.mutable.List
.
Dans l'esprit de la programmation fonctionnelle, nous baserons principalement nos exemples sur des aspects immuables du langage, avec des détours mineurs dans le côté mutable.
liste
Nous pouvons créer une liste de différentes manières:
scala> val names = List("Arthur", "Uther", "Mordred", "Vortigern")
names: List[String] = List(Arthur, Uther, Mordred, Vortigern)
Une autre approche pratique consiste à définir une liste à l'aide de l' ::
opérateur cons . Ceci joint un élément head avec la queue restante d'une liste.
scala> val name = "Arthur" :: "Uther" :: "Mordred" :: "Vortigern" :: Nil
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)
Ce qui équivaut à:
scala> val name = "Arthur" :: ("Uther" :: ("Mordred" :: ("Vortigern" :: Nil)))
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)
Nous pouvons accéder aux éléments de liste directement par leur index. N'oubliez pas que Scala utilise l'indexation de base zéro:
scala> name(2)
res7: String = Mordred
Certaines méthodes d'assistance courantes incluent:
list.head
, qui renvoie le premier élément:
scala> name.head
res8: String = Arthur
list.tail
, qui renvoie la queue d'une liste (qui comprend tout sauf la tête):
scala> name.tail
res9: List[String] = List(Uther, Mordred, Vortigern)
Ensemble
Set
nous permet de créer un groupe d'entités non répété. List
n'élimine pas les doublons par défaut.
scala> val nameswithDuplicates = List("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
nameswithDuplicates: List[String] = List(Arthur, Uther, Mordred, Vortigern, Arthur, Uther)
Ici, «Arthur» est répété deux fois, tout comme «Uther».
Créons un ensemble avec les mêmes noms. Remarquez comment il exclut les doublons.
scala> val uniqueNames = Set("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
uniqueNames: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)
Nous pouvons vérifier l'existence d'un élément spécifique dans Set en utilisant contains()
:
scala> uniqueNames.contains("Vortigern")res0: Boolean = true
Nous pouvons ajouter des éléments à un ensemble en utilisant la méthode + (qui prend par varargs
exemple des arguments de longueur variable)
scala> uniqueNames + ("Igraine", "Elsa", "Guenevere")res0: scala.collection.immutable.Set[String] = Set(Arthur, Elsa, Vortigern, Guenevere, Mordred, Igraine, Uther)
De même, nous pouvons supprimer des éléments en utilisant la -
méthode
scala> uniqueNames - "Elsa"
res1: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)
Carte
Map
est une collection itérable qui contient les mappages des key
éléments aux value
éléments respectifs , qui peuvent être créés comme:
scala> val kingSpouses = Map( | "King Uther" -> "Igraine", | "Vortigern" -> "Elsa", | "King Arthur" -> "Guenevere" | )
kingSpouses: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere)
Les valeurs d'une clé spécifique dans la carte sont accessibles comme suit:
scala> kingSpouses("Vortigern")res0: String = Elsa
Nous pouvons ajouter une entrée à Map en utilisant la +
méthode:
scala> kingSpouses + ("Launcelot" -> "Elaine")res0: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Elaine)
Pour modifier un mappage existant, nous rajoutons simplement la valeur-clé mise à jour:
scala> kingSpouses + ("Launcelot" -> "Guenevere")res1: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Guenevere)
Notez que puisque la collection est immuable, chaque opération d'édition retourne une nouvelle collection ( res0
, res1
) avec les modifications appliquées. La collection originale kingSpouses
reste inchangée.
4. Combinateurs fonctionnels
Maintenant que nous avons appris à regrouper un ensemble d'entités, voyons comment nous pouvons utiliser des combinateurs fonctionnels pour générer des transformations significatives sur de telles collections.
Pour reprendre les mots simples de John Hughes:
Un combinateur est une fonction qui construit des fragments de programme à partir de fragments de programme.An in-depth look at how combinators work is outside of this article’s scope. But, we’ll try to touch upon a high-level understanding of the concept anyhow.
Let’s take an example.
Suppose we want to find names of all queens using the kingSpouses
collection map that we created.
We’d want to do something along the lines of examining each entry in the map. If the key
has the name of a king, then we’re interested in the name of it’s spouse (i.e. queen).
We shall use the filter
combinator on map, which has a signature like:
collection.filter( /* a filter condition method which returns true on matching map entries */)
Overall we shall perform the following steps to find queens:
- Find the (key, value) pairs with kings’ names as keys.
- Extract the values (names of queen) only for such tuples.
The filter
is a function which, when given a (key, value), returns true / false.
- Find the map entries pertaining to kings.
Let’s define our filtering predicate function. Since key_value
is a tuple of (key, value), we extract the key using ._1
(and guess what ._2
returns?)
scala> def isKingly(key_value: (String, String)): Boolean = key_value._1.toLowerCase.contains("king")
isKingly: (key_value: (String, String))Boolean
Now we shall use the filter function defined above to filter
kingly entries.
scala> val kingsAndQueens = kingSpouses.filter(isKingly)
kingsAndQueens: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, King Arthur -> Guenevere)
2. Extract the names of respective queens from the filtered tuples.
scala> kingsAndQueens.values
res10: Iterable[String] = MapLike.DefaultValuesIterable(Igraine, Guenevere)
Let’s print out the names of queens using the foreach
combinator:
scala> kingsAndQueens.values.foreach(println)IgraineGuenevere
Some other useful combinators are foreach
, filter
, zip
, partition
, find
.
We shall re-visit some of these after having learnt how to define functions and passing functions as arguments to other functions in higher-order functions.
Let’s recap on what we’ve learned:
- Different ways of defining variables
- Various control-flow statements
- Some basics about various collections
- Overview of using functional combinators on collections
I hope you found this article useful. It is first in a series of articles to follow on learning Scala.
In part two, we’ll learn about defining classes, traits, encapsulation and other object-oriented concepts.
Please feel free to let me know your feedback and suggestions on how I can improve the content. Until then, ❤ coding.