Apprendre Scala de 0 à 60 ans: les bases

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 defpour 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: Stringcar 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-elsedé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.immutableou 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

Setnous permet de créer un groupe d'entités non répété. Listn'é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 varargsexemple 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

Mapest 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 kingSpousesreste 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.

  1. 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.