Section courante

A propos

Section administrative du site

Protocoles

Motivation

Clojure est écrit en termes d'abstractions. Il existe des abstractions pour les séquences, les collections, la possibilité d'appeler des fonctions,... De plus, Clojure fournit de nombreuses implémentations de ces abstractions. Les abstractions sont spécifiées par des interfaces hôtes, et les implémentations par des classes hôtes. Bien que cela ait suffi pour initialiser le langage, Clojure manquait alors de mécanismes d'abstraction et d'implémentation de bas niveau similaires. Les protocoles et les types de données ajoutent des mécanismes puissants et flexibles pour l'abstraction et la définition des structures de données, sans compromis sur les fonctionnalités de la plateforme hôte.

Les protocoles présentent plusieurs avantages :

Les protocoles ont été introduits dans Clojure 1.2.

Notions de base

Un protocole est un ensemble nommé de méthodes nommées et de leurs signatures, défini à l'aide de la fonction `defprotocol` :

  1. (defprotocol AProtocol
  2.   "Une chaîne de documentation pour une abstraction de protocole"
  3.   (bar [a b] "bar docs")
  4.   (baz [a] [a b] [a b c] "baz docs"))

La fonction `defprotocol` génère automatiquement une interface correspondante, portant le même nom que le protocole. Par exemple, pour un protocole `my.ns/Protocol`, une interface `my.ns.Protocol` est créée. Cette interface possède des méthodes correspondant aux fonctions du protocole, et le protocole fonctionne automatiquement avec les instances de cette interface.

Notez qu'il est inutile d'utiliser cette interface avec `deftype`, `defrecord` ou `reify`, car ces fonctions gèrent directement les protocoles.

  1. (defprotocol P
  2.   (foo [x])
  3.   (bar-me [x] [x y]))
  4.  
  5. (deftype Foo [a b c]
  6.   P
  7.   (foo [x] a)
  8.   (bar-me [x] b)
  9.   (bar-me [x y] (+ c y)))
  10.  
  11. (bar-me (Foo. 1 2 3) 42)
  12. = > 45
  13.  
  14. (foo
  15.  (let [x 42]
  16.    (reify P
  17.      (foo [this] 17)
  18.      (bar-me [this] x)
  19.      (bar-me [this y] x))))
  20.  
  21. > 17

Un client Java souhaitant participer au protocole peut le faire de manière optimale en implémentant l'interface générée par le protocole.

Les implémentations externes du protocole (nécessaires lorsqu'une classe ou un type externe doit participer au protocole) peuvent être fournies à l'aide du mot-clef `extend` :

  1. (extend AType
  2.   AProtocol
  3.    {:foo an-existing-fn
  4.     :bar (fn [a b] ...)
  5.     :baz (fn ([a]...) ([a b] ...)...)}
  6.   BProtocol
  7.     {...}
  8. ...)

La méthode `extend` prend un type/une classe (ou une interface, voir ci-dessous) et une ou plusieurs paires protocole + fonction (évaluée) :

Les protocoles sont entièrement concrétisés et prennent en charge les fonctionnalités de réflexion via `extends?`, `extenders` et `satisfies?`.

  1. (extend-type MyType
  2.   Countable
  3.     (cnt [c] ...)
  4.   Foo
  5.     (bar [x y] ...)
  6.     (baz ([x] ...) ([x y zs] ...)))
  7.  
  8.   ;se développe en :
  9.  
  10. (extend MyType
  11.   Countable
  12.    {:cnt (fn [c] ...)}
  13.   Foo
  14.    {:baz (fn ([x] ...) ([x y zs] ...))
  15.     :bar (fn [x y] ...)})

Recommandations pour l'extension

Les protocoles sont un système ouvert, extensible à tout type. Pour minimiser les conflits, veuillez tenir compte des recommandations suivantes :

Voir également cette discussion sur la liste de diffusion.

Extension via métadonnées

Depuis Clojure 1.10, les protocoles peuvent, en option, être étendus via des métadonnées par valeur :

  1. (defprotocol Component
  2.   :extend-via-metadata true
  3.   (start [component]))

Lorsque l'option `:extend-via-metadata` est activée, les valeurs peuvent étendre les protocoles en ajoutant des métadonnées où les clefs sont des symboles de fonction de protocole pleinement qualifiés et les valeurs sont des implémentations de fonctions. Les implémentations de protocole sont d'abord vérifiées pour les définitions directes (`defrecord`, `deftype`, `reify`), puis les définitions de métadonnées, et enfin les extensions externes (`extend`, `extend-type`, `extend-protocol`).

  1. (def component (with-meta {:name "db"} {`start (constantly "started")}))
  2. (start component)
  3. ;;=> "started"


Dernière mise à jour : Lundi, le 2 février 2026