Introduction à Scala : Méthodes et Fonctions

ScalaDans ce troisième volet de la série “Introduction à Scala”, je vais présenter la notion de méthodes et fonctions de Scala, qui est bien plus puissante que celle dans Java, dans la mesure où les fonction dans Scala sont des objets à part entière, pouvant être stockées dans une variable, passées ou retournées par une autre méthode, etc.

1. Présentation

La définition d’une méthode en Scala se présente comme suit:

[private|protected] [override] def nom [paramètres] [type de retour] [corps]

Ici, ce qui est entre [ ] dénote un élément optionnel.

Notez l’utilisation du mot clé def pour définir une fonction.
Dans Scala, et tout comme pour les définitions des classes, la visibilité par défaut est public, à moins qu’on précise explicitement la visibilité privée (via private) ou protégée (via protected).

Tout comme Java, les fonctions dans Scala sont virtuelles par défaut. Par contre, et à l’inverse de Java, pour redéfinir une méthode virtuelle, il faut le préciser explicitement avec le mot clé override, ce qui permet d’éviter quelques séances intensives de débogage pour comprendre l’origine de bugs bizarroïdes :) (C’est la même approche que celle prise par C#).

La partie paramètres d’une fonction est un peu différente de celle de Java et ce pour diverses raisons:
– Tout d’abord, à cause de la convention Pascal pour les déclarations (nom:Type)
– Mais aussi le fait que les parenthèses sont complètement optionnelles pour les méthodes sans arguments
– Et enfin, une fonction peut déclarer plusieurs listes de paramètres (j’y reviendrais plus tard)

J’ai déjà montré quelques exemples de méthodes avec les getters et les setters.
En voici une autre:

Code Scala

Cette méthode augmente l’age de la personne de n années.
Son invocation n’a rien d’exceptionnel, et se présente comme suit:

Code Scala

Cette méthode montre entre autres comment le type de retour est optionnel. D’ailleurs, elle ne le déclare pas. La définition exacte aurait été:

Code Scala

2. De l’objet au fonctionnel

Dans cette partie, je vais montrer l’aspect fonctionnel de Scala à travers un exemple où on part d’une implémentation à base des paradigmes de l’OO, puis en convertissant vers une approche plus fonctionnelle.

2.1. La version OO

Il s’agit tout simplement d’implémenter une méthode qui effectue un traitement d’une façon répétitive mais périodique.
Dans l’approche OO (et Java plus précisément), on commencerait par créer une interface qui définit une méthode.
Puis, on créerait une méthode qui prend cette interface comme paramètre ainsi que la période de répétition.

Voici une implémentation de cette approche en Scala :

Code Scala

J’ai défini un trait Action (la chose qui ressemble le plus à une interface dans Scala) avec une méthode doSomething.
J’ai ensuite crée une implémentation PingAction de ce trait qui ne fait qu’afficher le message “ping” dans la sortie standard (j’ai utilisé object au lieu de class tout simplement pour avoir un singelton).
J’ai aussi défini une méthode doPeriodically qui prend un Action et un entier comme paramètres, et qui répète l’exécution de l’action indéfiniment tout en “dormant” à chaque itération.

Voici à titre de comparaison la version Java de ceci:

Code Scala

2.2. La version fonctionnelle

En relisant le code précédent, on constate que malgré le fait qu’on a seulement besoin d’une méthode à exécuter périodiquement, on est obligé de construire plein de plomberie sans réel apport pour pouvoir y arriver, i.e. définir une interface, faire implémenter cette interface par une classe, et passage d’une instance de l’implémentation à la méthode d’exécution.

Or, ceci n’est aucunement nécessaire dans un langage fonctionnel comme Scala, où une méthode est un type de données comme les autres, c’est à dire que l’on peut écrire une méthode qui prend une méthode comme paramètre.

La définition du type d’une méthode en Scala se présente comme suit :
 
([paramètres]) => type-de-retour

Par exemple, une fonction mathématique comme le sinus ou le consinus admet le type suivant:

(Float) => Float

C’est à dire que c’est une fonction qui prend un float (réel) en paramètre et retourne aussi un réel.

Pour revenir à notre exemple, la méthode doPeriodically a besoin d’une méthode qui ne prend aucun paramètre et qui ne retourne rien, ce qui correspond au type :

() => Unit

Unit en Scala correspond au void de Java.
Voici donc une nouvelle version du programme précédent utilisant ces nouvelles données :

Code Scala

En gros, plus d’interface à implémenter ou d’objets à passer : on passe directement une méthode telle quelle à une autre fonction (via son nom), et celle-ci peut dès lors l’invoquer (toujours via son nom).

3. Concrètement

En fait, la notation “(params) => type-du-résultat” n’est que du sucre syntaxique fourni par Scala pour simplifier l’utilisation des fonctions.
Concrètement, les fonctions sont représentées par un ensemble de traits FunctionNN dénote le nombre de paramètres qu’elle prend.

Par exemple, le type de fonction ne prenant pas de paramètres est représenté par Function0[+R], où le R dénote le type de retour, tandis qu’une fonction prenant un seul paramètre est représentée par Function1[-T1, +R], où T1 est le type du paramètre et R le type de retour.

Dans tous les cas, ces traits définissent une méthode abstraite apply qui dans le cas de Function1 par exemple est définie comme suit:

apply (v1 : T1) : R

4. Higher order functions

En utilisant ce qu’on vient de voir, il devient possible de coder des trucs vraiment intéressant avec les fonctions, genre une méthode qui combine deux fonctions en une nouvelle fonction par exemple.
Ce type de fonctions s’appelle fonctions d’ordre supérieur, ou “Higer Order Functions“.
A titre d’exemple, voici une implémentation en Scala de la fonction “rond”, dénoté” par “o” en Mathématiques.

Petit rappel : f o g (x) = f(g(x))

Code Scala

Je commence par déclarer un nouveau type MathFunc qui est (Float) => Float, où encore une fonction prenant un float comme paramètre et retournant un float (C’est strictement optionnel, et uniquement dans le but de simplifier le code).

Je code ensuite la fonction rond qui prend deux MathFunc f et g en paramètre et retourne un MathFunc, i.e une nouvelle fonction de type MathFunc et qui devrait correspondre ) f o g.
Comme résultat, cette méthode retourne une nouvelle instance de Function1[Float, Float] dont la méthode apply retourne f(g(x)).

Dans la méthode main, je montre un exemple d’utilisation de cette fonction en combinant deux fonctions f et g que j’ai défini.

Remarque : Veuillez notez que la composition de fonctions que je viens de montrer est déjà implémentée dans Scala par les traits Function*, via la méthode compose.

Maintenant, à titre d’exemple (que je ne vais pas expliquer), et juste pour montrer la puissance de Scala et sa flexibilité, voici un bout de code qui permet de remplacer le “rond(f, g)” de l’exemple précédent par la notation plus naturelle “f o g” (La ligne surlignée en bleu clair):

Code Scala

5. Currying

Cette section a été corrigée suite à une remarque de SpaceGuid. Merci à lui

Le currying est une technique qui consiste à fixer la valeur de quelques uns des paramètres d’une fonction pour en créer une nouvelle qui prend les autres paramètres et qui retourne le résultat de l’originale.

Plus abstraitement, étant donné une fonction de type (X x Y) => Z (prend un paramètre de type X et un autre de type Y, et retourne un résultat de type Z), sa currification la transforme en fonction de type X => (Y => Z), c’est à dire une fonction prenant un paramètre de type X, et retournant une fonction qui prend un paramètre de type Y et retournant un résultat de type Z.

Par exemple, étant donné une fonction mul qui prend 2 paramètres x et y et qui retourne x*y (très intéressante et puissante comme fonction :D), on peut en dériver une fonction double qui prend un seul paramètre x et qui retourne son double (2*x) en fixant la valeur de y à “2”.

Le currying est très simple et intuitif à mettre en place avec Scala. Jugez vous en par vous même à travers cet exemple :

Code Scala

En gros, j’ai fait le currying de la fonction mul(x)(y) en fixant la valeur du paramètre x à 2 (via mul(2)), ce qui donne naissance à une nouvelle fonction qui ne prend plus qu’un seul paramètre et qui le multiplie par 2.

6. Exemple: Collections Scala

Les collections de Scala utilisent intensivement l’aspect fonctionnel du langage, ce qui permet de réaliser des traitements complexes sur une liste d’une façon intuitive et fluide.

Je vais commencer par montrer quelques exemple simples, pour finir avec d’autres plus complexes.

6.1. Exemples simples

Code Scala

Les commentaires expliquent ce que fait chaque méthode.
Notez que j’ai usé du type-inference du compilateur Scala pour pouvoir écrire

(x, y) => x + y

par exemple en omettant les types des arguments :
 
(x : Int, y : Int) => x + y

6.2. Ne joues pas au plus “Perl” avec Scala !

Maintenant, je vais présenter les même exemples de la section précédente, mais en introduisant la “Perl touch”, i.e. quelques hiéroglyphes magiques qui réduisent considérablement la taille de code (mais au risque de réduire autant la lisibilité du code).
L’hiéroglyphe magique en question dans Scala est le “_”, qui représente le joker (% dans le SQL, ? dans les expressions régulières, * dans les noms de fichiers, etc.).
Il a plusieurs connotations dans Scala et ce selon où il est utilisé.
Dans le cas des closures, il permet de représenter un paramètre d’une fonction.
Par exemple, “(x) => 2 * x” peut être ré-écrit en “2 * _“, et le compilateur Scala est assez malin pour savoir ce qui va dans le slot “_”. De même, le compilateur peut se débrouiller avec un truc comme “_ + _” qui correspond à “(x, y)=> x + y” peut devenir .
Ainsi, le code précédant devient :

Code Scala

6.3. Exemple plus complexe

Code Scala

No comment ! en fait si, un commentaire : dans Scala, ça bloque pas, c’est très fluide :D

Conclusion

Voilà, ainsi s’entame le troisième volet de l’introduction à Scala qui a servi, je l’espère, à présenter l’aspect fonctionnel de Scala via le.
Dans le prochain volet, ce serait le tour des traits, une sorte d’interfaces Java aux stéroïdes.

—-

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: