Clojure

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher
Clojure
Logo.
Auteur Rich Hickey
Développeurs Rich Hickey
Dernière version 1.8.0 ()Voir et modifier les données sur Wikidata
Paradigmes fonctionnel, multi-paradigme
Typage fort, dynamique
Influencé par Lisp, ML, Haskell, Erlang
A influencé Pixie, Rhine
Système d'exploitation JVM, Node.js, CLR
Licence Eclipse Public License
Site web http://clojure.org

Clojure est un langage de programmation fonctionnel compilé, multi-plateforme et destiné à la création de programmes sûrs et facilement distribuables. C’est un dialecte de Lisp. Il compile vers du bytecode Java, du code JavaScript et du bytecode .NET. Clojure est donc disponible sur la JVM, le CLR, les navigateurs et Node.js.

Philosophie[modifier | modifier le code]

Rich Hickey a développé Clojure parce qu’il voulait un Lisp moderne pour la programmation fonctionnelle, en symbiose avec la plateforme Java, et expressément orienté vers la programmation concurrente.

L’approche concurrentielle de Clojure est caractérisée par le concept d’identité[1], qui représente une série d’états immuables dans le temps. Comme les états sont des valeurs fixes, un nombre indéfini d’agents peut y accéder en parallèle, et la concurrence se résume alors à gérer les changements d’un état à un autre. Dans cette optique, Clojure propose plusieurs types de références mutables, chacun d’eux ayant une sémantique bien définie pour la transition inter-état.

Clojure considère les collections comme des séquences, et encourage la création de programmes consommant et produisant des séquences plutôt que des instances de classes. Cela permet de définir un programme en termes d’abstraction. Le programmeur ne se soucie pas de la façon dont un vecteur, une liste ou tout autre type de collection doit être itérée pour exprimer sa logique.

Caractéristiques du langage[modifier | modifier le code]

  • Développement dynamique à l’aide du REPL
  • Les fonctions sont des objets. La programmation par récursion est favorisée, plutôt que la programmation par boucle à effets de bord. La récursion terminale est supportée via le verbe recur
  • Séquences évaluées paresseusement
  • Grande variété de structures de données non mutables et persistantes
  • Programmation concurrente grâce à la STM, à un système d’agents, et à un système de variables dynamiques
  • Intégration au langage : en compilant en code binaire pour la Machine virtuelle Java, les applications en Clojure sont préparées et déployées dans la JVM ou un serveur d’application sans difficulté supplémentaire. Le langage fournit aussi des macros qui facilitent l’usage des API Java existantes. Les structures de données de Clojure implémentent toutes des interfaces Java standardisées, rendant aisée l’exécution de code écrit en Clojure depuis Java. Il en va de même avec les autres cibles de compilation.

Gestion des dépendances[modifier | modifier le code]

Les deux dépôts principaux sont les dépôts d’Apache Maven et Clojars. Comme Clojure est rétrocompatible avec Java, les dépendances sont traitées de la même façon que dans un projet Maven classique. Il est également possible d’importer un .jar quelconque en l’ajoutant simplement au classpath.

Outils[modifier | modifier le code]

Bien qu’il soit possible de créer des programmes Clojure avec Maven, on lui préfère largement Leiningen (à plus de 97% [2]). Leiningen fournit un meilleur REPL et facilite davantage l’automatisation des tâches de base. Exemple de dépendances avec Sparkling, un connecteur pour Apache Spark :

Leiningen

[gorillalabs/sparkling "1.2.5"]

Maven

<dependency>
  <groupId>gorillalabs</groupId>
  <artifactId>sparkling</artifactId>
  <version>1.2.5</version>
</dependency>

Anecdote : Clojure est lui même une dépendance du projet, se présentant comme un simple fichier .jar . Il suffit de modifier la version de la dépendance dans le fichier pom.xml ou project.clj et de relancer l’application pour exécuter le programme sur une autre version de Clojure.

Syntaxe[modifier | modifier le code]

Comme n’importe quel autre Lisp, la syntaxe de Clojure est basée sur les S-expressions. Ces dernières sont d’abord parsées en structures de données par un reader, avant d’être compilées. Clojure est un Lisp-1, et n’a pas pour objectif d’être compatible avec d’autres dialectes de Lisp.

Contrairement à d’autres Lisp, Clojure permet la représentation directe des structures suivantes : vecteurs, tables de hachage, ensembles. Ce qui rend aussi naturelle l’utilisation de ces structures en Clojure que les listes dans les autres Lisp.

Illustration[modifier | modifier le code]

Les commentaires commencent par deux points-virgules :

;; Ceci est donc un commentaire

Les crochets déclarent un vecteur, ici un vecteur d’entiers :

[1 2 3]

Les parenthèses, une liste chaînée :

'(1 2 3 4 5)

Le simple quote précédent la parenthèse est un caractère d’échappement. Voir Cas des listes chaînées. Les deux points, suivi d’un nom, déclarent un mots-clef. Les mot clef sont des chaînes de caractères internées et comparables par adresse. Les mots-clef sont souvent utilisés comme clefs dans les tables de hachage, ou comme remplacement des énumérations.

:mot-clef

:chat :chien :lapin

:administrateur :utilisateur :invité

Les accolades déclarent une table de hachage (ou map) :

{:nom "Clojure"
 :type :langage
 :famille :lisp
 :auteur "Rich Hickey"}

Les chaînes de caractères, délimitées par les guillemets anglais (ou double quotes) peuvent s'étendre sur plusieurs lignes.

"Une chaîne
 sur plusieurs
 lignes"

Le croisillon suivi d’accolades déclare un ensemble (ou set), ici un ensemble hétérogène :

#{1 2 3 :quatre "cinq" 6.0}

Code[modifier | modifier le code]

Clojure est un langage homoiconique (il s’écrit via ses propres structures de données). Un appel de fonction est donc fait d’une liste.

(+ 1 2 3)

Ici la fonction + reçoit 3 arguments et retourne la somme de ses arguments (6). Une liste peut contenir des sous-listes, formant ainsi des arbres.

(str "Résultat : " (+ 3 (/ (- 10 1) 2)))

;; => "Résultat : 15/2"

Cet appel de fonction retourne "Résultat : 15/2" . En effet, Clojure possède un type Ratio.

+, -, *, /, etc… ne sont pas des opérateurs, mais bien des fonctions. Il n’y a donc pas de priorité entre les opérateurs, car il n’y a pas d’opérateurs et car la syntaxe est toujours explicite.

Cas des listes chaînées[modifier | modifier le code]

Le code (+ 1 2 3) est un appel de fonction. Le code (1 2 3) est une déclaration de liste chaînée. Écrire (1 2 3) dans un REPL ou dans un programme provoquera une erreur car le compilateur tentera de faire référence au premier élément de la liste (1) en tant que fonction. Or 1 est un entier. Il faut donc échapper la liste chaînée en utilisant un simple quote ' . La bonne syntaxe est donc '(1 2 3) .

Cas des mots-clefs[modifier | modifier le code]

En Clojure, une fonction est un objet d’une classe implémentant l’interface IFn . C’est le cas des mots-clefs. Les appeler en tant que fonction sert à extraire des données d’une table de hachage.

;; déclaration d’une map nommée "ma-table"
(def ma-table {:nom "banane"
               :type :fruit
               :prix 0.50})
           
(:nom ma-table)
;; => "banane"

(:prix ma-table)
;; => 0.5

Ce code est équivalent :

(get ma-table :nom)
;; => "banane"

(get ma-table :prix)
;; => 0.5

Espaces de nommage[modifier | modifier le code]

Les espaces de nommage (namespaces) permettent de structurer le code en modules (similaires au packages en Java). La notation est constante et explicite. Le nom du namespace est en minuscule, les sous-namespaces sont séparés par un . . Le / sépare le namespace de son contenu. Par exemple la fonction split du namespace clojure.string se note clojure.string/split .

La déclaration d’un namespace suit les mêmes règles que pour les classes en Java : un namespace par fichier et un fichier par namespace.

Un namespace se déclare via ns , les namespaces peuvent être importés partiellement et/ou être aliasés. Voici un exemple de namespace fournissant une fonction estMineur et sont complément estMajeur :

;; déclaration du namespace nommé utilisateurs.validation
(ns utilisateurs.validation
    
    ;; chargement du namespace clojure.string, aliasé en 's'
    (:require [clojure.string :as s]))

;; Une fonction se déclare avec 'defn'

(defn estMineur
    "Ceci est une docstring.
    Cette fonction indique si l’âge donné
    correspond à un mineur ou non."
    [age]
    (< age 18))  ;; la dernière valeur de l’arbre est retournée


;; définition du complément
(def estMajeur (complement estMineur))


(defn formater-message
    "Affiche un message lisible indiquant si 
    l’utilisateur est majeur ou mineur."
    [utilisateur]
    (println
        (s/capitalize 
            (str "bonjour " (:nom utilisateur)
                 " vous êtes "
                 (if (estMajeur (:age utilisateur))
                     "majeur"
                     "mineur")
                  "."))))

(formater-message {:nom "Pierre" :age 12})

;; => Bonjour Pierre vous êtes mineur.

Macros[modifier | modifier le code]

Le système de macro de Clojure est semblable à celui que propose Common Lisp, exception faite de l’apostrophe inversée (backquote), appelée "apostrophe de syntaxe" (syntax quote) qui associe un symbole à son espace de nommage.

Il est par exemple possible de réécrire la fonction formater-message bien plus proprement grâce à une macro : la threading macro :

(defn formater-message
    [utilisateur]
    (->
        (str "bonjour " (:nom utilisateur)
             " vous êtes "
             (if (estMajeur (:age utilisateur)) "majeur" "mineur")
             ".")
         (s/capitalze)
         (println)))

La macro threading-first -> insère en seconde position (premier argument) le résultat de l’application de fonction précédente. Cela permet de composer les fonctions sous forme d’une liste d’étapes à exécuter séquentiellement.

Plateformes[modifier | modifier le code]

Bien que la JVM soit la cible de compilation principale, ClojureScript permet de compiler du code Clojure en JavaScript. Le code produit est alors compatible avec les navigateurs supportants la norme ECMAScript 3+ et Node.js.

Clojure se veut symbiotique avec sa plateforme et ne tente pas de cacher les fonctionnalités natives de celle-ci. Clojure permet donc, via des fichiers .cljc de définir des règles de compilation. Il est très courant de trouver des bibliothèques compatibles avec plus d’une plateforme.

Exemples[modifier | modifier le code]

Créer un nouveau programme avec Leiningen et lancer le REPL :

> lein new app nom-du-programme
> cd nom-du-programme
> lein repl

En Clojure[modifier | modifier le code]

"Bonjour tout le monde!" :

(println "Bonjour tout le monde!")

"Bonjour tout le monde" dans une interface graphique Swing :

(javax.swing.JOptionPane/showMessageDialog nil "Bonjour tout le monde!")

Ou encore :

(ns wikipedia.core 
    (:import (javax.swing JOptionPane)))

(JOptionPane/showMessageDialog nil "Bonjour le monde!")

En ClojureScript[modifier | modifier le code]

;; Affiche "Bonjour le monde" dans la console du navigateur
(.log js/console "Bonjour le monde")

;; En utilisant alert
(js/alert "Bonjour le monde")

Notes et références[modifier | modifier le code]

  1. (en) « On State and Identity », Rich Hickey, clojure.org (consulté le 1er mars 2010)
  2. (en) « State of Clojure 2015 », Cognitech Blog, clojure.org (consulté le 2 juin 2016)

Liens externes[modifier | modifier le code]