Pourquoi Clojure ?
Clojure est un langage de programmation dynamique, fonctionnel et concurrent qui a été créé en 2007. Il est principalement utilisé pour le développement de logiciels, des systèmes distribués et des applications web modernes. Un cas d'usage concret serait la création d'une application backend pour une plateforme de gestion de contenu ou bien un système de calcul scientifique où les performances et la simplicité sont cruciales.
Prerequis
Avant de commencer avec Clojure, il est préférable d'avoir :
- Connaissance de base en programmation
- Une installation de Java 8 ou plus récente, car Clojure s'exécute sur le JVM (Java Virtual Machine)
Outils à installer :
- Le JDK : version 8 ou plus tard
- Un éditeur de code comme IntelliJ IDEA avec le plugin Clojure ou Emacs avec les packages
clojure-modeetcider - La dernière version de Leiningen : gestionnaire de projet pour Clojure
Concepts fondamentaux
1. Fonctionnalité
Clojure est un langage fonctionnel, ce qui signifie que les fonctions sont des "citoyens de première classe". Elles peuvent être passées en tant qu'arguments à d'autres fonctions et elles peuvent aussi être retournées comme résultats.
;; Définir une fonction simple
(defn add [a b]
(+ a b))
(add 3 4) ;; Résultat : 7
2. Immutabilité
L'immutabilité est un principe fondamental de Clojure, où les données ne peuvent pas être modifiées une fois créées. Cela permet d'éviter des bugs difficiles à déboguer et rend le code plus facile à reasonner.
;; Définir une variable immuable
(defn create-vector [x y]
(vector x y))
(create-vector 1 2) ;; Résultat : [1 2]
3. Threads et Concurrency
Clojure a été conçu pour être concurrent, ce qui signifie qu'il peut exécuter plusieurs tâches en même temps sans risquer de conflits entre les threads.
;; Lancer une fonction dans un nouveau thread
(defn async-print [msg]
(future (println msg)))
(async-print "Salut !")
Mise en pratique : projet fil rouge
Étape 1 : Création du Projet
Créons un simple gestionnaire de tâches. Nous utiliserons Leiningen pour créer le projet.
lein new app task-manager
cd task-manager
Étape 2 : Structure du Projet
Le projet aura une structure basique comme suit :
src/task_manager/core.clj: Contiendra la logique principale de notre application.resources/public/index.html: La page HTML pour l'interface utilisateur.
Étape 3 : Définition des Fonctions
Ouvrons src/task_manager/core.clj et ajoutons quelques fonctions de base :
(ns task-manager.core)
(def tasks [])
;; Ajouter une tâche
(defn add-task [task]
(conj tasks task))
;; Afficher les tâches
(defn print-tasks []
(doseq [task tasks]
(println task)))
;; Exemple d'utilisation
(add-task "Faire du code")
(print-tasks) ;; Résultat : Faire du code
Étape 4 : Interface Utilisateur
Créez une page HTML simple dans resources/public/index.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Gestionnaire de Tâches</title>
</head>
<body>
<h1>Gestionnaire de Tâches</h1>
<input type="text" id="task-input" placeholder="Ajouter une tâche...">
<button onclick="addTask()">Ajouter</button>
<script>
function addTask() {
const input = document.getElementById('task-input');
const task = input.value;
if (task) {
fetch('/add-task', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ task })
});
input.value = '';
}
}
function renderTasks(tasks) {
const list = document.getElementById('tasks-list');
list.innerHTML = '';
tasks.forEach(task => {
const li = document.createElement('li');
li.textContent = task;
list.appendChild(li);
});
}
fetch('/get-tasks')
.then(response => response.json())
.then(data => renderTasks(data.tasks));
</script>
</body>
</html>
Étape 5 : API REST
Ajoutons une API REST pour gérer les tâches. Nous utiliserons Ring et Compojure pour cela.
;; Ajoutez ces dépendances à project.clj
:dependencies [[org.clojure/clojure "1.10.3"]
[ring/ring-core "1.8.2"]
[ring/ring-jetty-adapter "1.8.2"]
[compojure/compojure "1.6.1"]]
(require '[clojure.core :refer :all])
(require '[compojure.core :refer :all])
(require '[ring.adapter.jetty :refer [run-jetty]])
(defn add-task [{:keys [params]}]
(let [task (get-in params [:form-params "task"])]
{:status 200
:body {:message "Tâche ajoutée" :task task}}))
(defn get-tasks []
{:status 200
:body {:tasks tasks}})
(defroutes app-routes
(POST "/add-task" request add-task)
(GET "/get-tasks" request get-tasks))
(def app
(-> app-routes
wrap-defaults site-defaults))
(run-jetty #'app {:port 3000})
Étape 6 : Exécution
Pour exécuter l'application :
lein ring server
Ouvrez votre navigateur et allez sur http://localhost:3000. Vous devriez voir une interface utilisateur simple où vous pouvez ajouter des tâches et les afficher.
Erreurs fréquentes et debugging
1. Variable non définie
## ❌ Mauvais
(println x)
## ✅ Correct
(def x 5)
(println x)
2. Syntaxe incorrecte
## ❌ Mauvais
(defn my-func []
(println "Salut"))
## ✅ Correct
(defn my-func []
(println "Salut"))
3. Accès à une variable locale
## ❌ Mauvais
(let [x 5])
(println x)
## ✅ Correct
(let [x 5]
(println x))
Pour aller plus loin
- Fonctionnalité avancée : Apprenez comment utiliser des macros et les fonctions récursives.
- Concurrentie : Explorez les concepts de concurrence avec Atoms, Refs et Agents.
- Tests unitaires : Utilisez Midje ou Speclj pour écrire des tests unitaires.
Défi pratique :
Créez une application CLI qui permet d'ajouter, de supprimer et de lister des tâches en utilisant des arguments de ligne de commande.