Aller au contenu

Clojure

Un article de Wikipédia, l'encyclopédie libre.

Clojure
Logo.

Date de première version Voir et modifier les données sur Wikidata
Paradigmes fonctionnel, multi-paradigme
Auteur Rich Hickey
Développeurs Rich Hickey
Dernière version 1.12.0 ()[1]Voir et modifier les données sur Wikidata
Typage fort, dynamique
Influencé par ML
Common Lisp
LispVoir et modifier les données sur Wikidata
A influencé Pixie, Rhine, Elixir
Écrit en JavaVoir et modifier les données sur Wikidata
Système d'exploitation JVM, Node.js, CLR
Licence Licence Eclipse[2]Voir et modifier les données sur Wikidata
Site web clojure.orgVoir et modifier les données sur Wikidata
Extension de fichier clj, cljs, cljc, edn et cljrVoir et modifier les données sur Wikidata

Clojure est un langage de programmation fonctionnelle compilé, multi-plateforme et destiné à la création de programmes sûrs et facilement distribuables. C’est un dialecte de Lisp. Il transpile 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.

Histoire du langage

[modifier | modifier le code]
Rich Hickey, créateur de Clojure

Rich Hickey est le créateur du langage Clojure[3]. Avant Clojure, il a développé dotLisp, un projet similaire basé sur la plate-forme .NET[4], et trois tentatives antérieures pour assurer l'interopérabilité entre Lisp et Java : une "interface Java en langue étrangère pour Common Lisp" (Java foreign language interface for Common Lisp, jfli)[5], une "interface pour objets étrangers pour Lisp" (Foreign Object Interface for Lisp, FOIL)[6] et une "interface conviviale pour Lisp pour les servlets Java" (Lisp-friendly interface to Java Servlets, Lisplets)[7].

Hickey a passé environ deux ans et demi à travailler sur Clojure avant de le rendre public, une grande partie de ce temps travaillant exclusivement sur Clojure sans aucun financement extérieur. À la fin de cette période, Hickey a envoyé un courriel annonçant le langage à quelques amis de la communauté Common Lisp.

Le processus de développement est mené par la communauté[8] et est géré à la page du projet JIRA Clojure[9] où les problèmes peuvent être soumis. Les discussions générales sur le développement se déroulent au sein du Clojure Google Group[10]. Tout le monde peut soumettre des problèmes et des idées, mais pour contribuer aux correctifs, il faut signer le Clojure Contributor agreement[11]. Les problèmes sur le JIRA sont traités par une équipe d'examinateurs et finalement Rich Hickey approuve les changements[12].

Le nom "Clojure", selon Hickey, est un jeu de mots sur le concept de "fermeture" dans la programmation (en anglais, closure), qui incorpore les lettres C, L et J pour C#, Lisp et Java respectivement - trois langages qui ont eu une influence majeure sur la conception de Clojure[13].

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é[14], 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-états.

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 (en)
  • 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
  • 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.

Bien qu’il soit possible de créer des programmes Clojure avec Maven, Leiningen est plus utilisé (à plus de 97 %[15]). 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>

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.

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 (en), 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 de certaines structures : vecteurs, tables de hachage, ensembles. Ceci 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édant la parenthèse est un caractère d’échappement (voir cette section). Le deux-points, suivi d’un nom, déclare un mot clef. Les mots clefs sont des chaînes de caractères internées et comparables par adresse. Les mots clefs 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}

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 trois 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 ou être aliasés. Voici un exemple de namespace fournissant une fonction estMineur et son 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.

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/capitalize)
         (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 supportant 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.

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

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

Hello world :

(println "Bonjour tout le monde !")

Hello world 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 tout 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. a et b « Release 1.12.0 », (consulté le )
  2. « https://clojure.org/community/license » (consulté le )
  3. (en) Paul Krill, « Clojure inventor Hickey now aims for Android », sur InfoWorld, (consulté le )
  4. « Google Groupes », sur groups.google.com (consulté le )
  5. (en) « jfli », sur SourceForge (consulté le )
  6. (en) « foil - Foreign Object Interface for Lisp », sur SourceForge (consulté le )
  7. (en) « Lisplets », sur SourceForge (consulté le )
  8. « Clojure - Development », sur clojure.org (consulté le )
  9. « - JIRA », sur clojure.atlassian.net (consulté le )
  10. « Google Groupes », sur groups.google.com (consulté le )
  11. « Clojure - Contributor Agreement », sur clojure.org (consulté le )
  12. « Clojure - Workflow », sur clojure.org (consulté le )
  13. « Google Groupes », sur groups.google.com (consulté le )
  14. (en) « On State and Identity », Rich Hickey, clojure.org (consulté le )
  15. (en) « State of Clojure 2015 », Cognitech Blog, clojure.org (consulté le ).

Liens externes

[modifier | modifier le code]