Tutoriel pour apprendre le langage Scala

Débuter avec Scala

Madusudanan

Traducteur

Laethy

Ce cours est la première partie d'une série de tutoriels pour vous appendre la programmation scala.

Si vous êtes un programmeur Java, il y a beaucoup de choses qui peuvent être difficiles à assimiler, et la façon la plus simple de vous en sortie est d'apprendre.

- Titre : Tutoriel pour apprendre le langage Scala

- Auteur : Madusudanan

- Traducteur : Laethy

- Parution : 17 août 2017

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Madusudanan. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Initiation à Scala et configuration de l'environnement

- I-A. Votre premier Hello World !!

- II. Les variables

- III. Syntaxe des tableaux

- IV. Référence contre immutabilité des valeurs

- V. Immuabilité sous le capot

- VI. Comparaison et contraste de val et final

- VII. Les types de données dans Scala

- VIII. Inférence de type

- IX. Initialisation des variables

- X. Types d'annotations

- XI. Attribution de type

- XII. Valeur Paresseuse

I - Initiation à Scala et configuration de l'environnement

Il existe de nombreux tutoriels sur Internet qui parlent de la mise en place de Scala, alors je ne vais pas continuer à les aborder.

La configuration que je recommanderais est la suivante.

- Système d'exploitation : Mac / Linux de préférence

- L'édition communautaire Intellij IDEA avec le plugin Scala installé

Si vous avez fini de les configurer, commencez.

I-A - Votre premier Hello World !!

Si vous avez des connaissances en Java, notez que scala est légèrement différent dans sa représentation. Passons directement à un code.

1. object Test {

2. def main(args : Array[String]){

3. println("Hello world")

4. }

5. }

Je vous recommande de mettre ce code dans votre éditeur Intellij et ensuite faites un clique droit pour l'exécution. Si tout va bien, votre console affichera Hello World.

Toutes nos félicitations !! Vous êtes l'auteur de votre premier Hello world.

Maintenant, abordons cela étape par étape.

La première chose à noter, c'est que tout le code se trouve dans un bloc d'objet. Les programmeurs Java pourraient trouver cela confus. Garder cette question pour l'instant, la prochaine section l'abordera de manière plus détaillée. Contrairement à Java, les noms de classe ne doivent pas correspondre aux noms de fichiers, ce n'est pas un grand avantage, mais nous avons cette liberté ici.

La prochaine chose est la syntaxe étrange de def main (). Pour commencer, def est un mot clé pour déclarer les méthodes. Nous traiterons les méthodes plus en détail dans un tutoriel à venir.

S'il existe une méthode, il peut y avoir des arguments. Dans notre cas, c'est Array [String]. Ceci est similaire à la méthode principale de java, où un ensemble de chaînes de caratères peut être un argument sur la méthode principale. Ceci peut être utilisé pour les configurations de démarrage ou autre chose, mais l'utilisation est complètement facultative.

Ceci est suivi d'un appel de la méthode println () qui affiche les instructions à la console. Si vous utilisez un EDI (vous devriez), vous pouvez tracer tout l'appel en maintenant la touche Ctrl + un clique sur la méthode. Les programmeurs Python devraient trouver cette syntaxe familière et, en fait, pour un programmeur, il n'y a rien d'autre que l'affichage d'une chaîne de caratères dans la console. Les points-virgules sont facultatifs en scala à la différence de java et le compilateur repose sur des sauts de ligne pour identifier le prochain bloc de code .

Mais creusons un peu plus profondément. Pour les programmeurs Java, une impression générale sur la console sera comme System.out.println. Alors, comment est-ce différent ?

La réponse est qu'il n'est pas si différent, println () est une méthode de classe appelée Predef.scala, qui appelle alors la méthode println de Console.scala qui appelle out.println sur PrintStream.class ou PrintStream.java si vous avez le Code source joint, sinon le décompilateur d'Intellij montre le code décompilé. L'impression aussi bien pour scala que pour java se retrouve dans le même appel de méthode. Comme indiqué précédemment, scala est construit sur la JVM et peut interagir avec le code Java de façon transparente, il y aurait une raison spécifique si la mise en œuvre était différente, sinon ce serait juste de réinventer la roue.

En fait, il existe de nombreux autres exemples qui utilisent les bibliothèques java dans notre parcours pour apprendre à utiliser Scala.

Un simple Hello world a ouvert de nombreux sujets à aborder, trois en particulier

- Méthodes (couvert dans les tutoriels ultérieurs)

- Objets et classes (couvert dans les tutoriels ultérieurs)

- Interférence de types - La raison pour laquelle scala est une langue dynamique typiquement statique - expliquée ci-dessous

II - Les variables

J'aurais dû expliquer les types de données avant de passer aux variables, mais il y a des différences fondamentales dont je veux parler afin que nous puissions mieux les comprendre.

v et val sont deux mots-clés qui sont utilisés pour déclarer des variables dans Scala.

v est utilisé pour déclarer des variables mutables et val déclarer des variables immuables. Mais quel type de variables sont-ils ? Pour les types primitifs de données, d'où vient le concept de mutabilité ? Les données primitives par elles-mêmes sont immuables, c'est-à-dire que leur type ne peut être modifié une fois déclaré, mais leurs valeurs sont mutables, c'est-à-dire qu'elles peuvent être modifiées.

En premier lieu, il est difficile de comprendre pourquoi le concept de mutabilité agit sur les variables et non sur les objets, ceci est expliqué ci-dessous dans la section des types de données, il n'y a pas de types primitifs dans Scala, tous sont des objets.

III - Syntaxe des tableaux

Il s'agit simplement d'un bref aperçu de la manière dont les tableaux sont déclarés en Scala, ce qui est légèrement différent de celle de java.

Certains tableaux peuvent être déclarés comme ci-dessous :

val array = Array(10,12,23,44)

Ceci est différent de la déclaration java où nous utilisons des accolades pour des valeurs de tableau fixe.

int[] array = {10,12,33,55};

Et pour des valeurs inconnues, nous utilisons des crochets.

int[] array = new int[4];

En Scala, les crochets sont utilisés pour indiquer le type.

val array : Array[Int] = Array(10,12,23,44)

Cela équivaut au symbole <> de java.

ArrayList<Integer> rows = new ArrayList<>();

Nous verrons plus en profondeur les collections dans les tutoriels à venir.

IV - Référence contre immutabilité des valeurs

Si val est immuable, alors il ne peut pas être modifié ? Est-ce similaire au mot-clé final de Java ou est-ce en rapport à l'immutabilité de la chaîne de caractères ?

Regardons l'exemple de code ci-dessous, pour nous aider à mieux comprendre.

1. var myVar = 10

2. //fonctionne correctement

3. myVar = myVar + 10

5. val myNum = 6

6. //Erreur de compilation

7. //Réaffectation à val

8. myNum = myNum + 10

Si vous exécutez le code ci-dessus, vous constaterez une erreur lors de la compilation elle-même, causée par la réaffectation à val. Si vous utilisez un EDI, l'erreur s'affiche lorsque vous tapez le code en raison de la précompilation que fait l'IDE .

La première chose n'est certainement pas l'immutabilité de la chaîne de caractères étant donné qu'elle n'est pas visible pour le programmeur et est contrôlée au niveau de la compilation. Question suivante, est-ce semblable à final en Java?

Vue de loin, cela semble signifier qu'une fois qu'une valeur lui est attribuée, elle ne peut pas être modifiée, mais à l'intérieur de la JVM, final n'a rien à voir avec l'immutabilité et elle est utilisée pour que les classes ne puissent pas être étendues et dans le cas des méthodes, elles ne peuvent pas être remplacées.

Considérons le code Java ci-dessous pour démontrer cette différence.

final ArrayList<Integer> arrList = new ArrayList<Integer>();

/*
Cela n'entraîne pas d'erreur, nous sommes en train de muter l'objet lui-même. Si elle était mutable, cela entraînerait une erreur à quelque chose comme ça ne peut pas être changé
*/
arrList.add(20);

/*
Cela entraîne une erreur car nous modifions la référence et non l'objet lui-même
*/
arrList = null;

Si vous essayez final avec des types primitifs, sa valeur ne peut pas être modifiée. Cela signifie-t-il que les types primitifs sont immuables ? La raison pour laquelle l'erreur survient est parce que Java passe par valeur les types primitifs, il n'est pas logique de passer par référence car ce ne sont pas du tout des objets. Donc, si une variable est modifiée, sa référence (peut être considérée comme emplacement dans la mémoire) change à cause du mécanisme de passage par valeur et non parce que ces primitives sont immuables.

Le point important à comprendre est que l'immutabilité de référence et de valeur importe peu en scala car scala n'a pas de types primitifs et n'a que des objets.

Donc, chaque fois que nous parlons d'immutabilité dans Scala, nous parlons de l'immutabilité des références.

Les variables immuables ont certains bénéfices de performance et s'approchent de la notion d'écriture de code sans effets secondaires.

V - Immuabilité sous le capot

Poussons notre compréhension de l'immutabilité encore plus loin en examinant le code d'octet émis en décompilant les fichiers de classe scala générés.

Nous déclarons une classe nommée Parent et un val à l'intérieur.

1. class Parent {

2. val x = 10

3. }

En voyant le bytecode émis, nous pouvons voir qu'il se traduit par un type primitif java, il n'y a rien de spécial en ce qui concerne le temps d'exécution.

javap -c filename.class donne le code décompilé ci-dessous :

1. public class Parent {

2. public int x();

3. Code:

4. 0: aload_0

5. 1: getfield      #13     // Field x:I

6. 4: ireturn

8. public Parent();

9. Code:

10. 0: aload_0

11. 1: invokespecial #19     // Method java/lang/Object."<init>":()V

12. 4: aload_0

13. 5: bipush        10

14. 7: putfield      #13     // Field x:I

15. 10: return

16. }

Il est évident que val est juste une restriction de temps de compilation et n'a rien à voir avec le code d'octet émis.

Nous pouvons utiliser cette approche de la lecture du code d'octet décompilé pour comprendre les choses plus profondément, mais dans la plupart des cas, cela n'est pas nécessaire.

VI - Comparaison et contraste de val et final

D'autre part scala a également le mot-clé final qui fonctionne de façon similaire.

Une meilleure façon de visualiser la différence entre val et final est à travers un exemple.

Prenons une simple classe parent :

class Parent {

val age = 10

}

Contrairement à java, scala peut également remplacer les variables.

1. class Child extends Parent{

3. override val age = 30

5. def printVal ={

6. println(age)

7. }

9. }

Maintenant, si nous déclarons la variable age de type final dans la classe Parent, alors il ne serait pas possible pour la classe Child de la remplacer et cela provoquerait une erreur comme ci-dessous :

class Child extends Parent{
override val age = 30
println(age)
}

C'est un exemple réel de l'endroit où l'on utiliserait final.

Notez que nous ne rompons pas l'immuabilité ici en annulant val dans la classe Child. La classe Child crée une instance propre comme une chaîne de caractère dans le pool de chaînes de caractères java.

VII - Les types de données dans Scala

Scala a les mêmes types de données que Java, avec la même mémoire et la même précision.

Tous les types de données sont des objets et des méthodes peuvent être appelées dans ces fichiers comme vous le feriez sur un objet.

1. val t = 69

2. //Imprime 'E' la valeur ASCII de E est 69

3. println(t.toChar)

5. val s = "Hello World"

6. // NDT : commentaire confus pour moi

7. println(s.charAt(2))

À l'heure actuelle, une autre question aurait été posée ? Où sont les types dans le code scala ?

Contrairement à java, où nous déclarons des variables avec des types de données, puis donnons un nom de variable, scala a quelque chose appelée l'inférence de type (voir les rubriques ci-dessous), mais n'y aller pas encore, il y a une raison pour laquelle je l'ai séparé de la section des variables .

VIII - Inférence de type

Si vous ne connaissez pas le terme, ce n'est que la déduction des types au moment de la compilation. Attend, n'est-ce pas ce que signifie le typage dynamique ? Eh bien non, notez que j'ai dit la déduction des types, cela est radicalement différent de ce que font les langages typés dynamiquement, et une autre chose est que cela se fait au moment de la compilation et non au moment de l'exécution.

De nombreuses langages ont cette notion intégrée, mais l'implémentation varie d'un langage à l'autre. Cela pourrait être compliqué au début, mais il sera plus clair avec des exemples de code.

Passons au Scala REPL pour une certaine expérimentation.

De l'image ci-dessus, il est évident qu'il n'y a rien d'extraordinaire, les types des variables sont déduites automatiquement aux meilleurs types jugés appropriés et au moment de la compilation.

Voici un code pour mieux comprendre.

1. val x = 20

3. //affiche à la console

4. //inféré comme un entier

5. println(x+10)

7. //Quelque chose d'aussi stupide comme ci-dessous causera une erreur de compilation

8. val z = 40

9. println(z * "justastring")

Allez-y et jouez avec ces variables, vous êtes protégé par la sécurité du type au moment de la compilation, alors n'hésitez pas à mettre le désordre.

Si vous êtes curieux de savoir dans quelles classes les variables s'étendent, vous pouvez creuser plus profondément et trouver une classe appelée AnyVal. Cela fait partie d'un sujet entièrement différent du système de type unifié Scala, qui n'est que la hiérarchie des classes.

IX - Initialisation des variables

En Scala, vous ne pouvez pas simplement créer une variable et la laisser non initialisée.

1. class Variables{

2. val x // Mauvaise déclaration de valeur

3. println (x)

4. }

C'est un choix de conception des développeurs de scala. La raison évidente est qu'il ne faut pas laisser les variables non initialisées afin d'éviter des exceptions de pointeur nul.

Le seul endroit où nous n'attribuons pas de valeurs aux variables est au niveau des classes abstraites. Nous approfondirons cette notion lorsque nous aborderons les classes scala.

X - Types d'annotations

Scala nous autorise à mentionner le type explicitement.

val y : Integer = 20

Ces genres d'annotations de type sont importantes dans les API / méthodes public.

1. def getInfoFromBackend() =  {

3. val dataList = List(1,"Literature",2,"Science")

5. dataList

7. }

Sans les informations de type explicitement annotées, les développeurs qui les utilisent auront une confusion. N'oubliez pas que tous les langages de la JVM n'ont pas d'inférence de type comme java et, par conséquent, une modification du type peut interrompre le code client (consommant).

Une meilleure version serait comme ci-dessous :

1. def getInfoFromBackend() = List {

3. val dataList = List(1,"Literature",2,"Science")

5. dataList

7. }

Ce concept ne s'applique pas seulement aux paramètres de la méthode, mais aussi aux variables déclarées à l'aide du mot-clé val. L'exemple présente simplement l'idée d'une manière plus compréhensible.

XI - Attribution de type

Les attributions de type sont quelque chose de plus complexe. Il s'agit de déclarer au compilateur le type de retour d'une opération que vous allez effectuer.

Un cas d'utilisation typique serait la conversion de type dans java.

1. int x = 20;

3. //Conversion valide

4. System.out.println((byte) x);

6. //erreur d'exécution

7. Object s = new Object();

8. System.out.println((byte) s);

Le code ci-dessus entraînerait une exception / erreur d'exécution comme ci-dessous :

20
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Byte
at JavaExample.main(JavaExample.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

En scala, si nous utilisons l'attribution de type, le code ne compilerait même pas.

object Demo extends App{
val x : Byte = new Object // L'expression du type Object n'est pas conforme au type Byte attendu

Il n'y a pas de différence de syntaxe entre l'annotation de type et l'attribution de type et cela entraîne souvent une confusion entre les deux sujets.

Nous aurions pu faire pareille en java en utilisant la conversion de type lors de l'exécution.

val x = new Object().asInstanceOf[Byte]

Dans le cas ci-dessus, vous pouvez constater qu'il n'y a pas d'erreur au moment de la compilation et il en résultera la même trace de la pile d'exception, c'est-à-dire java.lang.ClassCastException au moment de l'exécution. Les attributions de type peuvent être très utiles lors de la conversion de type. Vous pouvez vérifier la sécurité du type au moment de la compilation plutôt qu'à l'exécution pour vous assurer qu'il n'y a pas de code de bug.

XII - Valeur Paresseuse

Comme le suggère le nom, la valeur paresseuse dans scala est similaire à un val, mais sa valeur n'est évaluée que lorsque la variable est utilisée.

1. import scala.io.Source._

3. object ReadFileExample extends App{

5. println(System.getProperty("user.dir"))

7. lazy val lines = fromFile(System.getProperty("user.dir") + "/file1.txt").getLines

9. println(lines)

11. }

Je vous encourage à essayer cela vous-même. D'abord en commentant println(lines), vous pouvez voir que cela n'a pas entraîné d'erreur même si le fichier n'existait pas.

Mais ceci est un exemple fonctionnel et vous pouvez mettre un fichier réel au niveau supérieur du projet actuel / mettez le à un emplacement spécifique vous-même et que le programme imprime simplement le contenu du fichier.

Ceci est contrôlé au moment de la compilation, car un accès variable peut être connu au moment de la compilation.

Très utile dans des situations telles qu'une fenêtre de chargement de fichier dans un navigateur. L'utilisateur peut ou ne peut pas télécharger le fichier, il est donc préférable de différer / évaluer avec précaution jusqu'à ce que l'événement se produise.

Cela met fin à cette publication, nous avons commencé avec les éléments importants. Je vous encourage à relire le tutoriel pour mieux le comprendre si vous n'avez pas encore pris connaissance de la documentation pertinente sur Internet.

Scala n'est pas facile, mais ce n'est pas difficile non plus si on fait un pas à la fois et qu'on apprend les choses. Mon objectif n'est pas de tout enseigner, mais simplement d'indiquer une direction beaucoup plus spécifique.