IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Introduction au langage Haskell
Par stendhal666

Le , par stendhal666

0PARTAGES

- vous prendrez bien une petite cuillère de Haskell ?
Dans le précédent billet, j’ai utilisé LISP pour montrer ce que pouvait être une approche bottom-up fondée sur la modularité qu’apporte la composition de fonction. Pourtant, LISP n’est pas –et de loin- le langage qui favorise le plus cette approche. Dans ce billet et le suivant, je voudrais introduire certaines tournures syntaxiques des langages plus fonctionnels –j’utiliserai Haskell, mais vous pourriez trouver des choses équivalentes dans Ocaml, Scala, F# ou Erlang. Je reprendrai l’exemple de l’évaluation d’une main au poker, développé dans le billet précédent, pour faciliter la comparaison.

A tout seigneur, tout honneur
NB : une implémentation partielle d’un shell Haskell est disponible à l’adresse https://tryhaskell.org/

Une fonction s’appelle sans parenthèses, ni extérieures, ni postérieures :
>sqrt 4
2

Les opérateurs sont également des fonctions dont la seule particularité est que leur nom est composé uniquement de caractères spéciaux :
> 1 + 1
2

Ils peuvent être appelés comme des fonctions à condition de les placer entre parenthèses :
> (+) 1 1
2

Et une fonction peut être appelée comme un opérateur à condition de placer son nom entre des « backquotes », des apostrophes inclinées comme des accents graves :
> 5 `min` 6
5

Des fonctions monogames
Les fonctions n’ont en Haskell qu’un seul argument, même quand elles semblent en avoir plusieurs… Par exemple, si vous écrivez
> min 5 6
« min 5 » est d’abord évaluée, retournant une fonction qui prend un argument et qui équivaut à :
> \x -> min 5 x -- vous découvrez la syntaxe des fonctions lambda : \ arg1 arg2 … argN -> expression

Cette particularité est à la base de l’application « partielle » des fonctions :
> map (+1) [1,2,3]
[2,3,4]
Ou bien encore on peut définir une fonction inc (incrémente) de la façon suivante :
inc = (+1) -- nommer les arguments n’est pas nécessaire

Pour autant, par facilité de langage, je ferai par la suite comme si les fonctions pouvaient prendre plusieurs arguments.

Des fonctions typées
Il est encore trop tôt pour introduire le système de types de Haskell, qui a sa part de complexité, je le signale simplement et donne un aperçu de sa syntaxe :

processHand :: (Int -> Int -> Bool) -> [Int] -> [[Int]]

processHand (qui tient le même rôle que dans le billet précédent, préparer une main à son évaluation), prend comme argument :
- une fonction qui prend en argument deux Int et retourne un Bool : (Int -> Int -> Bool)
- une liste de Int : [Int]
- et retourne une liste de listes de Int : [[Int]]

Donner le type d’une fonction lors de sa déclaration n’est le plus souvent pas nécessaire (le compilateur fait un travail d’inférence de type pour s’en charger à notre place) mais c’est une bonne pratique et l’intervention de l’Homme est de temps en temps encore nécessaire (n’ayez pas trop peur de la grande méchante A.I.)

Des fonctions joliment composées
Haskell propose un opérateur de composition, le point :
(f . g) x < = > f (g x) -- la parenthèse autour de (f . g) est nécessaire car l’application de la fonction a une précédence plus élevée que la composition

On peut utiliser autant de fonctions que souhaité dans la composition. Pour reprendre l’exemple de processHand :
Code Haskell : Sélectionner tout
processHand p = reverse . sortBy (comparing length) . splitIf p . sort    -- joli, non ?
NB : reverse inverse l’ordre d’une liste, sortBy trie une liste selon un critère donné en premier argument, splitIf est une fonction « maison » qui est le complément de split-if-not définie dans le billet précédent et sort trie la liste dans l’ordre ascendant.
La lecture doit donc se faire de droite à gauche, mais est aisée : sort, puis splitIf p, … puis reverse.

Si vous nommez l’argument de la fonction composée, il faut l’introduire par l’opérateur $. C’est un opérateur d’application de fonction, mais de précédence très faible :
Code Haskell : Sélectionner tout
1
2
f . g x  < = > f . (g x)    -- oups ! 
f . g $ x < = > (f . g) x

Comment traduire « pattern matching » ?
L’expression « pattern matching » pourrait peut-être être traduite comme « détection de motif » ; mais j’ai bien cherché sans trouver de traduction satisfaisante. Alors à votre bon cœur, messieurs-dames, laissez vos trouvailles en commentaire.

Code Haskell : Sélectionner tout
1
2
fac 0 = 1    --lorsque l’argument est 0 
fac n = n * fac (n-1)   --pour tout autre argument

C’est bien une seule fonction qui est définie. L’opération à réaliser est déterminée en fonction de l’argument effectivement reçu par la fonction. Le « pattern matching » peut être aussi compliqué que nécessaire et analyser, par exemple, une liste qui lui est donnée :

Code Haskell : Sélectionner tout
1
2
len [] = 0 
len ( x :xs) = 1 + len xs    -- lie x au premier élément de la liste, xs au reste de la liste

Récapitulons
Pour récapituler ce que nous avons appris dans ce billet, et introduire le mot-clé « where » qui permet de définir des termes utilisés précédemment dans la fonction, je vous propose une première implémentation de split-if :
Code Haskell : Sélectionner tout
1
2
3
4
5
6
7
8
  
splitIf :: (Int -> Int -> Bool) -> [Int] -> [[Int]] 
splitIf _ [x] = [[x]]   -- _ ou « wildcard » montre qu’on ignore l’argument 
splitIf p (x:xs) = if not $ p x y then addConcat x next else addSplit x next 
<div style="margin-left:40px">where next = splitIf p xs; 
<div style="margin-left:40px">y = head . head $ next; 
addConcat x (y:ys) = (x:y):ys ; 
addSplit x xs = [x] : xs</div></div>

Promis, nous ferons mieux la prochaine fois !

Exercice :
- installer Haskell (je vous conseille fortement ghc –compilateur- et ghci –interpréteur en ligne de commande), ainsi qu’un éditeur qui prenne en charge la syntaxe Haskell (Notepad++, Editra)
- feuilletez les bibliothèques standard d’Haskell grâce à Hoogle qui permet également de voir le code source
- reposez-vous bien avant le prochain billet, qui contiendra deux trois choses pour se tordre convenablement le cerveau

Une erreur dans cette actualité ? Signalez-nous-la !