{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Présentation générale\n", "\n", "[Le site officiel](http://scala-lang.org)\n", "\n", "Scala est un langage de programmation développé à l'École Polytechnique Fédérale de Lausanne (EPFL) par Martin Odersky.\n", "\n", "Scala est un langage _multi-paradigme_ qui associe la programmation orienté objet et la programmation fonctionnelle.\n", "Cela lui assure une grande polyvalence en permettant au développeur de choisir le paradigme le plus approprié pour son problème. \n", "Scala est totalement interopérable avec Java.\n", "Il est compilé en Java bytecode, et exécuté sur la JVM.\n", "Scala profite ainsi de la maturité de Java : on peut notamment appeler du Java depuis du Scala, et donc profiter des bibliothèques Java existantes.\n", "\n", "Du fait de son lien avec l'écosystème Java, le développement de Scala prend en compte les critiques faites à Java, ce qui lui vaut son surnom de \"Java on steroids\".\n", "Scala propose par exemple un mécanisme de composition évitant les problèmes de l'héritage multiple, qui n'est pas possible en Java.\n", "\n", "Scala est de plus en plus utilisé : la base de code de Twitter est ainsi principalement en Scala.\n", "De même, les développements liés aux big data se font de plus en plus en Scala, grâce à l'existence de frameworks dédiés.\n", "\n", "Le but du cours est de vous familiariser avec Scala, ses concepts, sa syntaxe... \n", "N'hésitez pas à utiliser l'interpréteur interactif : les bouts de code présentés sont des invitations à l'expérimentation.\n", "\n", "\n", "# Concepts fondamentaux\n", "\n", "## Un peu de syntaxe\n", "\n", "Afin de faciliter la transition depuis Java, la syntaxe de Scala est assez proche de celle de Java. \n", "Les commentaires suivent la même syntaxe qu'en Java. \n", "Notez également que les conventions de nommage reprennent celles de Java.\n", "\n", "Parmi les différences, le point-virgule de fin d'instruction _n'est pas obligatoire_.\n", "Il est nécessaire pour séparer des instructions sur la _même ligne_, cas assez rare. \n", "\n", "## Variables et valeurs\n", "\n", "Scala distingue _variables_ et _valeurs_.\n", "Le contenu d'une _variable_ est modifiable, mais pas celui d'une _valeur_.\n", "On peut voir une _valeur_ comme une variable `const` en C++. \n", "Les _valeurs_ sont aussi appelées _variables immuables_ (_immutable variables_ en anglais).\n", "\n", "Cette distinction est importante car elle est liée au paradigme fonctionnel de Scala.\n", "Idéalement, le paradigme fonctionnel interdit les effets de bord.\n", "Dans ce cadre (certes idéal), la notion de variable (une case mémoire dont on peut modifier la valeur) n'est donc pas pertinente. \n", "D'un point de vue plus pragmatique, l'identification des variables immuables permet au compilateur d'optimiser la gestion de la mémoire.\n", "\n", "Les valeurs sont introduites par `val` et les variables par `var`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "Name: Compile Error\n", "Message: :20: error: reassignment to val\n", " x = 1 // error: a val is immutable\n", " ^\n", "StackTrace: " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// This a one-line comment\n", "/* This is a multi-line comment\n", "which continues on the next line */\n", "val x = 10\n", "x = 1 // error: a val is immutable" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "var x = 10\n", "x = 1 // OK: a var is mutable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Typage et inférence de types\n", "\n", "Vous avez remarqué que les déclarations de \"x\" ci-dessus ne contiennent pas d'informations de typage.\n", "Pourtant, Scala a un typage statique (à la compilation), et non dynamique (à l'exécution).\n", "\n", "Comme dans beaucoup de langages fonctionnels statiquement typés, le compilateur est capable de déterminer (presque) seul les types des variables et valeurs utilisés dans le programme. C'est ce qu'on appelle l'*inférence de types*.\n", "Pour cela, il s'appuie sur les opérations effectuées sur les valeurs et variables pour en déterminer le type.\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "int" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x.getClass()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En réalité, il est toujours possible pour le développeur d'indiquer les types des valeurs, variables, fonctions...\n", "D'une part, cela participe à la lisibilité du code.\n", "D'autre part, cela peut permettre de forcer l'utilisation de certains types qui ne sont pas ceux que le compilateur aurait choisi par défaut.\n", "On utilise pour cela des *annotations de type explicites*." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "byte" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "val x: Byte = 10\n", "val y: Int = 10\n", "x.getClass()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tout est expression\n", "\n", "En Scala, tout est une _expression_.\n", "En particulier, une séquence d'instructions (c'est-à-dire un programme) est une expression.\n", "Le type et la valeur d'une séquence sont le type et la valeur de sa dernière instruction." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "hello\n", "(3,1)\n" ] } ], "source": [ "var y: Int = 0\n", "// x takes the value of the sequence, which is the value of its last instruction\n", "// note that the sequence has side effects (writes y and prints something)\n", "val x: Int = { println(\"hello\"); y=1; 2; 3 }\n", "println(x,y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ainsi, le corps d'une fonction est une expression.\n", "Le type de retour de la fonction est celui de son corps, la valeur de retour d'une fonction est la valeur de son corps (considéré comme une expression). \n", "Concrètement, cela signifie que le mot-clef `return` est inutile, comme nous allons le voir." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fonctions\n", "\n", "Une fonction est introduite par le mot-clef `def`.\n", "Une fonction a un nom, des arguments et un corps. \n", "**Le corps doit être introduit par le signe `=`.**\n", "\n", "**Attention** Si le corps d'une fonction n'est pas introduit par le signe `=`, alors le type de retour de la fonction est `Unit`, *quel que soit le type de son corps*.\n", "\n", "_Les types des arguments doivent être explicites_, mais le type de retour peut être implicite. \n", "Le type de retour d'une _fonction récursive_ doit aussi être explicite. \n", "Le type d'une fonction est `A => B`, où `A` est le type des arguments et `B` le type de retour. \n", "On peut aussi définir des fonctions anonymes qui n'ont pas de nom.\n", "\n", "Dans les exemples ci-dessous, notez l'absence d'instruction `return`." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "Name: Compile Error\n", "Message: :2: error: ':' expected but ')' found.\n", " def foo(x) = { x+1 }\n", " ^\n", "StackTrace: " ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// error: type of argument is missing\n", "def foo(x) = { x+1 }\n", "foo(2)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "4\n", "5\n", "true\n" ] } ], "source": [ "// implicit return type\n", "def foo1(x: Int) = { x+1 }\n", "println(foo1(2))\n", "\n", "// explicit return type\n", "def foo2(x: Int): Int = { x+2 }\n", "println(foo2(2))\n", "\n", "// alternative syntax: both the argument and return type are explicit\n", "// NB: '=>' is a type constructor, but also a keyword\n", "def foo3: Int => Int = { x => x+3 }\n", "println(foo3(2))\n", "\n", "// example of use of an anonymous function that checks whether its argument is even\n", "val l = List(1,2,3)\n", "println(l.exists( (x: Int) => { x % 2 == 0 } ))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "Name: Compile Error\n", "Message: :22: error: type mismatch;\n", " found : Unit\n", " required: Int\n", " val y: Int = bar(5) // error: explicit type of y does not match type of bar(5)\n", " ^\n", "StackTrace: " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def bar(x: Int) { x } // \"hidden\" error: without = sign, return type is Unit\n", "val y: Int = bar(5) // error: explicit type of y does not match type of bar(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tout est objet\n", "\n", "Scala est un langage objet _pur_, c'est-à-dire que **tout** est objet, contrairement à Java qui distingue des types élémentaires (`int`, `boolean` ...). \n", "Noter que les fonctions sont aussi des objets.\n", "\n", "De plus, toutes les classes dérivent d'une même classe `Any`, ce qui assure que toutes les classes disposent d'un ensemble de méthodes, telles que `toString()` ou `hashCode()`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "\n" ] } ], "source": [ "println(3.toString()) // '3' is an instance of class 'Int'\n", "def foo(x: Int) = { x+1 } // foo is a function\n", "println((foo _).toString()) // foo is also an object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes et objets\n", "\n", "Les classes en Scala permettent de rassembler des _attributs_ et des _méthodes_.\n", "Les attributs peuvent être immuables (valeurs) ou non (variables). \n", "Les classes ont des _paramètres_, qu'on peut voir comme des arguments de son \"constructeur\".\n", "Les paramètres peuvent aussi décrire directement des attributs. \n", "Une instance de la classe est créée en exécutant tout le code présent dans le corps de la classe.\n", "Pour faire le parallèle avec Java, le constructeur est le corps de la classe." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "start constructor\n", "stop constructor\n", "start constructor\n", "age and year of birth mismatch\n", "stop constructor\n", "Doe\n", "20\n" ] }, { "data": { "text/plain": [ "Name: Compile Error\n", "Message: :22: error: value yob is not a member of Student\n", " println(s.yob) // error: yob is a parameter, but not an attribute of the class\n", " ^\n", "StackTrace: " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// 'name' is an immutable attribute of Student\n", "// 'age' is a mutable attribute of Student\n", "// Note how the attributes can be declared with a corresponding parameter\n", "class Student(val name: String, var age: Int, yob: Int) {\n", " println(\"start constructor\")\n", "\n", " if (age != 2017-yob)\n", " println(\"age and year of birth mismatch\")\n", "\n", " println(\"stop constructor\")\n", "}\n", "val r = new Student(\"Foo\", 20, 1997)\n", "val s = new Student(\"Doe\", 20, 1994)\n", "println(s.name)\n", "println(s.age)\n", "println(s.yob) // error: yob is a parameter, but not an attribute of the class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On peut définir des attributs dans le corps de la classe, pas uniquement dans les paramètres. \n", "On peut également définir plusieurs constructeurs (attention à la syntaxe). \n", "Les méthodes suivent la même syntaxe que les fonctions." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My name is John Doe\n", "My name is anonymous\n" ] } ], "source": [ "class Person(val name: String) {\n", " // an attribute that is not a parameter\n", " val ID = name + \"00\"\n", " \n", " // a method (i.e. a function within the class)\n", " def print() { println(\"My name is \" + name) }\n", " \n", " // define another constructor with no argument,\n", " // this constructor calls the \"default\" constructor declared with the class\n", " def this() = this(\"anonymous\")\n", "}\n", "def p1 = new Person(\"John Doe\")\n", "def p2 = new Person()\n", "p1.print()\n", "p2.print()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Singleton\n", "\n", "Scala n'autorise pas de méthode ni d'attribut statique dans les classes, contrairement à Java.\n", "En contrepartie, Scala simplifie la création de singleton (une classe qui a exactement une instance).\n", "Les singletons sont introduits par le mot-clef `object`, ce qui conduit à utiliser \"singleton\" et \"objet\" comme des synonymes. \n", "Un singleton peut porter le même nom qu'une classe.\n", "Ceci permet de créer pour une classe un singleton portant le même nom dont les attributs et méthodes sont ceux qui auraient été `static` en Java.\n", "Ce motif est appelé _compagnonage_. On parle de _singleton compagnon_, ou d'_objet compagnon_.\n", "\n", "**NB :** Une classe peut ne pas avoir d'objet compagnon, tout comme un objet peut exister sans accompagner de classe. Le motif classe/compagnon est cependant fréquemment employé pour distinguer méthodes statiques et dynamiques." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foobar\n" ] } ], "source": [ "// a class\n", "class Foo(n: String) {\n", " val name = n\n", " def bar = println(n)\n", "}\n", "// a companion object that acts as a factory for class Foo\n", "// In Java, 'makeFoo' would rather be a static method of the class 'Foo'\n", "object Foo {\n", " def makeFoo(n: String) = new Foo(n)\n", "}\n", "\n", "val f = Foo.makeFoo(\"foobar\")\n", "f.bar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercices de prise en main\n", "\n", "Vous pouvez effectuer ces exercices dans le notebook ou bien dans l'interpréteur interactif Scala, s'il est installé.\n", "\n", "1. Écrivez une fonction `hello` qui renvoie la chaîne de caractères \"Hello world!\".\n", "\n", "2. Écrivez une fonction `print` qui imprime la chaîne de caractères passée en argument.\n", "\n", "3. Écrivez une fonction `apply` qui applique la fonction passée en premier argument à la chaîne de caractères passée en deuxième argument.\n", "\n", "4. Écrivez une fonction `compose` qui prend en argument deux fonctions sur des entiers et qui renvoie leur composition.\n", "\n", "5. Écrivez une classe `Student`, avec 3 attributs : `name`, `surname` et `id` qui est un identifiant _unique_.\n", "N'hésitez pas à utiliser un objet compagnon si nécessaire." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Apache Toree - Scala", "language": "scala", "name": "apache_toree_scala" }, "language_info": { "name": "scala", "version": "2.10.4" } }, "nbformat": 4, "nbformat_minor": 2 }