79
Clojure (oubliez vos préjugés) Christophe Grand @cgrand Laurent Petit @petitlaurent Mix-it 2011, 5 avril

Mix it 2011 - Clojure

Embed Size (px)

DESCRIPTION

Clojure introduction given during the French Mix it 2011 event.

Citation preview

Page 1: Mix it 2011 - Clojure

Clojure(oubliez vos préjugés)

Christophe Grand @cgrandLaurent Petit @petitlaurent

Mix-it 2011, 5 avril

Page 2: Mix it 2011 - Clojure

Motivations

Page 3: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle

Page 4: Mix it 2011 - Clojure

Réduire la complexité accidentelle

"Out of the tar pit" ( http://ben.moseley.name/frp/paper-v1_01.pdf )Distinguer la complexité inhérente de l'accidentelle

"Le mythe du mois-homme" ( Frederick P. Brooks, Jr.)

Ch. 16: "Pas de balle d'argent: l'essence et la substance en génie logiciel"

Page 5: Mix it 2011 - Clojure

Réduire la complexité accidentelle

"Out of the tar pit" ( http://ben.moseley.name/frp/paper-v1_01.pdf )Distinguer la complexité inhérente de l'accidentelle

"Le mythe du mois-homme" ( Frederick P. Brooks, Jr.)

Ch. 16: "Pas de balle d'argent: l'essence et la substance en génie logiciel"

"Comme Aristote, je divise [les difficultés de la technologie logicielle] en essence - celles qui sont inhérentes à la nature du logiciel - et en substance - celles qui gênent aujourd'hui sa production, mais n'y sont pas inhérentes."

(F. P. Brooks dans Le mythe du mois-homme)

Page 6: Mix it 2011 - Clojure

Simple fiable

orthogonalpeu de syntaxe

peu de conceptspeu de surprises

larges abstractions

Réduire la complexité accidentelle

Page 7: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Page 8: Mix it 2011 - Clojure

Programmation concurrente

Mécanismes de bas niveau (lock, synchronized)

OOP introduit de la complexité Myriades de petits états répartis très difficiles à manipuler de manière cohérente

"#antioopargs OO, as practiced e.g. in Java, conflates identity, state and behavior"

@stilkov, expert java reconnu

Page 9: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Expressivité maximale

Page 10: Mix it 2011 - Clojure

Expressivité maximale du langage

ConcisionLe moins "cérémonieux possible" !

Factorisation

Oser passer aux génériques !

Adaptation"Si le problème ne vient pas au langage, le langage ira au problème" !

Degrés d'abstraction

Rester dans le langage, en bonne compagnie !

Page 11: Mix it 2011 - Clojure

Expressivité maximale du langage

Approche combinée Top Down et Bottom UpDécomposition fonctionnelle .... (top down)Mais construction d'un "langage" pour le domaine du problème (bottom up)Equivalent des APIs dites "fluent" en java

= mini-langages "embarqués" (DSLs)

/* exemple d'API "fluent" dans JPA */ em.createNamedQuery("Student.findByNameAgeGender") .setParameter("name", name) .setHint("hintName", "hintValue") .getResultList();

Page 12: Mix it 2011 - Clojure

Expressivité maximale du langage

Encore 1 fois, l'OOP par défaut est dans le collimateur !

Plus difficile de généraliser les algorithmes Plus cérémonieux

Mais ne pas jeter le bébé avec l'eau du bain

"Composition, interfaces, héritage : 2 bonnes idées sur 3, pourquoi tt le monde est parti avec la 3ème ?"

"Vu sur Twitter" (#antioop)

Page 13: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Expressivité maximale Langage généraliste

Page 14: Mix it 2011 - Clojure

Langage généraliste

Plateforme industrielleinteropérableperformantdéployableréutilisation de l'existant

Pas un langage de niche

performant dans le cas généralsans perdre les qualités du langage (pas de contorsions)

Page 15: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Expressivité maximale Langage généraliste

Page 16: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Expressivité maximale Langage généraliste

Page 17: Mix it 2011 - Clojure

MotivationsRéduire complexité accidentelle Programmation concurrente

Expressivité maximale Langage généraliste

Page 18: Mix it 2011 - Clojure

Caractéristiques

Page 19: Mix it 2011 - Clojure

Caractéristiques

Base fonctionnelle

Page 20: Mix it 2011 - Clojure

Base fonctionnelle

Briques de base simplesFonctionsStructures de données génériques

Fonctions sans effet de bord

Sur leurs argumentsSur leur environnement Retournant une valeur non altérable=> Naturellement thread-safe !

"It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures."

- Alan J. Perlis

Page 21: Mix it 2011 - Clojure

Base fonctionnelle

Sans hypothéquer le pouvoir d'abstraction/indirectionFonctions en paramètresFonctions en valeur de retour ("closures")Fonctions polymorphes

Application : le concept de "séquence" en Clojure:

"It is better to have 100 functions operate on one data abstraction than 10 functions on 10 data structures"

- Attributed to Rich Hickey

Page 22: Mix it 2011 - Clojure

Caractéristiques

Base fonctionnelle

Etats Managés

Page 23: Mix it 2011 - Clojure

Etats managés

La base fonctionnelle seule est insuffisante

Besoin d'orchestrer la mutation des étatsPour écrire des programmes concurrents fiablesMais aussi pour bien organiser son code !

Confiner la mutation des états comme on confine le traitement des entrées-sorties !

On installe cette orchestration au coeur du langage = idiomatique et orthogonal

Page 24: Mix it 2011 - Clojure

Etats managés

Séparation stricte identité / valeur

Identité = stable au cours du tempsEtat = Valeur des caractéristiques d'une identité à un instant tValeur = ensemble immuable de caractéristiques

Le temps passe, les identités sont stables, leurs valeurs changent

PRIMITIVES DE HAUT NIVEAU POUR GERER LES TRANSITIONS D'ETAT

Page 25: Mix it 2011 - Clojure

Caractéristiques

Base fonctionnelle

Etats Managés

Lisp

Page 26: Mix it 2011 - Clojure

LISP

Syntaxe simple et uniformeRapproche donnée et code

"Data is code, and code is data" En utilisant les macros à la compilation : "code writing code"

Alternative à la répétition de certains patternsPossibilité de rester dans le langage plus longtemps (pas de génération de code depuis UML !)

Suppression possible des derniers "boilerplates" du code

Page 27: Mix it 2011 - Clojure

LISP

Dynamique REPL = "Read, Eval, Print, Loop"

Le code est évalué (en fait compilé) ligne à ligneOn peut continuer d'évaluer du nouveau code au runtimeOn peut recharger les valeurs des fonctions/variables à chaudOn peut évaluer n'importe quel code de test/initialisation à chaud

Très pratique pour le prototypage, le debug ....

Un langage "AGILE" !

Page 28: Mix it 2011 - Clojure

Caractéristiques

Base fonctionnelle

Etats Managés

Lisp

JVM

Page 29: Mix it 2011 - Clojure

JVM

JVMpragmatismeécosystèmeentreprise

Compiléinteractif ou AOT

*warn-on-reflection*

PerformantJava en benchmark

Hotspot-friendly

Page 30: Mix it 2011 - Clojure

InteropZéro overhead java.util.* Runnable & Callable

JVM

Page 31: Mix it 2011 - Clojure

Caractéristiques

Base fonctionnelle

Etats Managés

Lisp

JVM

Page 32: Mix it 2011 - Clojure

Syntaxe

Page 33: Mix it 2011 - Clojure

Commentaire ; ligne #_(bloc de code)

Chaîne "Bonjour Lyon !\nBonjour Mix-IT !"

Nombres 42 2/3 3.14 1e6 12.34M 42N

Caractères \a \newline \space

Booléens true false

null nil

Regexes #"a*b*" #"\"[^\"]*\""

Symboles ma-fonction java.util.List s/split

Mots clés :nom :xml/tag ::local

Vecteurs [4 5 6 "cueillir des cerises"]

Maps {:key "value", 69000 "Lyon", nil -1}

Sets #{1 "mixed" :bag}

Appel de fonction (println "Bonjour Maître !")

Structure de contrôle

(if (test x) (print "then") (print "else"))

Page 34: Mix it 2011 - Clojure

Commentaire ; ligne #_(bloc de code)

Chaîne "Bonjour Lyon !\nBonjour Mix-IT !" java.lang.String

Nombres 42 2/3 3.14 1e6 12.34M 42N java.lang.Longjava.lang.Double

Caractères \a \newline \space java.lang.Character

Booléens true false java's true & false

null nil java's null

Regexes #"a*b*" #"\"[^\"]*\"" java.util.Pattern

Symboles ma-fonction java.util.List s/split

Mots clés :nom :xml/tag ::local

Vecteurs [4 5 6 "cueillir des cerises"] java.util.List

Maps {:key "value", 69000 "Lyon", nil -1} java.util.Map

Sets #{1 "mixed" :bag} java.util.Set

Appel de fonction (println "Bonjour Maître !")

Structure de contrôle

(if (test x) (print "then") (print "else"))

Page 35: Mix it 2011 - Clojure

Fonctions

Page 36: Mix it 2011 - Clojure

Fonctions "anonymes" : définitions

Explicite :(fn [args] ...code...)

Page 37: Mix it 2011 - Clojure

Fonctions "anonymes" : définitions

Explicite :(fn [args] ...code...) Contractée 1 argument :#(foo (bar %) (baz %))

Page 38: Mix it 2011 - Clojure

Fonctions "anonymes" : définitions

Explicite :(fn [args] ...code...) Contractée 1 argument :#(foo (bar %) (baz %)) Contractée n arguments :#(foo (bar %1) (baz %2))

Page 39: Mix it 2011 - Clojure

Fonctions "anonymes" : définitions

Description Forme explicite Forme contractée

Incrémente de 2 (fn [x] (+ x 2)) #(+ % 2)Ajoute la taille de e à sum (fn [sum e]

(+ sum (count e)) )#(+ %1 (count %2))

Explicite :(fn [args] ...code...) Contractée 1 argument :#(foo (bar %) (baz %)) Contractée n arguments :#(foo (bar %1) (baz %2))

Page 40: Mix it 2011 - Clojure

Fonctions "anonymes" : Utilisation

Exemple 1 : Ajoute 2 à tous les éléments d'une séquence (map f s) => Transforme la séquence s en appliquant à chaque élément x la fonction f : (f x)

; Forme expliciteuser=> (map (fn [x] (+ x 2)) [1 2 3])(3 4 5) ; Forme contractée user=> (map #(+ % 2) [1 2 3])(3 4 5)

Page 41: Mix it 2011 - Clojure

Fonctions "anonymes" : Utilisation

Exemple 2 : Somme totale des tailles des éléments d'une liste (reduce f val s) => Calcule une valeur v de manière itérative en calculant d'abord v0 = (f val e0) puis v1 = (f v0 e1), etc. v = (f vn-1 en) Ou encore :on parcourt s avec une valeur qu'on "accumule" d'un élément à l'autre, et on retourn la valeur accumulée user=> (reduce + 0 [1 1 1])3

Page 42: Mix it 2011 - Clojure

Fonctions "anonymes" : Utilisation

Exemple 2 : Somme totale des tailles des éléments d'une liste; Forme expliciteuser=> (reduce (fn [sum e] (+ sum (count e))) 0 ["a" "bc" "def"]) 6

Page 43: Mix it 2011 - Clojure

Fonctions "anonymes" : Utilisation

Exemple 2 : Somme totale des tailles des éléments d'une liste; Forme expliciteuser=> (reduce (fn [sum e] (+ sum (count e))) 0 ["a" "bc" "def"]) 6 ; Forme contractée user=> (reduce #(+ %1 (count %2)) 0 ["a" "bc" "def"])6

Page 44: Mix it 2011 - Clojure

Fonctions globales et constantes

Déclarées dans un espace de noms, un "namespace"~ package

Documentation en ligne, introspectable ;; Fichier mix_it/clojure.clj (ns mix-it.clojure)

(def add-2 (fn [x] (+ x 2))) (defn add-2 [x] (+ x 2)) (defn add-2 "Incrémente x de 2" [x] (+ x 2))

Page 45: Mix it 2011 - Clojure

Structures "persistantes"

(not= "Persistant" "stockage disque")

Valeur originale persiste après "modification"undo for freepas de doute sur le comportement des lib tierces

Données immuables"modification" = création d'une instance modifiée

Modifications performantesPas de copie brutale, stocké en arbrePartage de structure (les branches inchangées)Opérations en O(log32(n)) ~ O(1) pour des n réalistes

Page 46: Mix it 2011 - Clojure

Structures "persistantes"

user=> (def v1 [:a :b :c])#'user/v1user=> (def v2 (assoc v1 0 :A))#'user/v2user=> [v1 v2][[:a :b :c] [:A :b :c]]

Page 47: Mix it 2011 - Clojure

Structures "persistantes" : opérations génériques

Collections count, conj, seq

Vectors vector, vec, get, assoc, pop, ...

Maps hash-map, assoc, dissoc, merge, zipmap, ...

Sets hash-set, disj, union, difference, intersection, ...

Associative structures

update-in, assoc-in

http://clojure.org/data_structures

Page 48: Mix it 2011 - Clojure

Structures "persistantes" : exemple

user=> (def mix-it {:ou "Lyon", :stats {:participants 198, :speakers 25}})#'user/mix-it

user=> (update-in mix-it [:stats :participants] + 2){:ou "Lyon", :stats {:participants 200, :speakers 25}}

Page 49: Mix it 2011 - Clojure

Orthogonalité

Page 50: Mix it 2011 - Clojure

Orthogonalité : (lazy) sequences

Iterators done right

Vue sequentielle d'une collectionfonction seq => vue "naturelle"d'autres fonctions: rseq subseq rsubseq vals keys...

Immuables bien entendu

Page 51: Mix it 2011 - Clojure

Orthogonalité : (lazy) sequences

Abstraction d'une liste liée : 3,5 fonctionsconstructeur(cons 1 (cons 2 nil)) => (1 2) firstrest (ou next)user=> (let [s [1 2 3 4]] [(first s) (rest s) (next s)])[1 (2 3 4) (2 3 4)]

seq implicite(cons 1 [2 3]) => (1 2 3)(first {:a 1 :b 2 :c 3}) => [:a 1] (next {:a 1 :b 2 :c 3}) => [:b 2 :c 3]

Page 52: Mix it 2011 - Clojure

Orthogonalité : (lazy) sequences

3 fonctions et demi ?rest vs nextlaziness

Majorité des séquences lazy

non simultanément réalisées en mémoire "medium éphémère de traitement"pipelines !!! vers l'infini et au delà !

gros volumessimplification des algos

Page 53: Mix it 2011 - Clojure

Orthogonalité : lazy sequences

Page 54: Mix it 2011 - Clojure

Accès uniforme aux champs

Polymorphisme non intrusif

Clear upgrade path

Page 55: Mix it 2011 - Clojure

Accès uniforme aux données

Implémentation plus ou moins évoluée ...Donnée non typée :

(def dnt {:nom "Mix-it", :participants 200})

Donnée typée :(defrecord Event [nom participants])(def dt (Event. "Mix-it" 200))

... mais accès aux données uniforme côté "client" :

user=> (:nom dnt)"Mix-it"user=> (:participants dt)200

Page 56: Mix it 2011 - Clojure

Polymorphisme non intrusif

Ce matin, un lapin ... (defn mk-lapin [couleur] {:espece :lapin, :couleur couleur})(defn mk-chasseur [arme] {:espece :chasseur, :arme arme}) (def l1 (mk-lapin :gris))(def l2 (mk-lapin :noir)) (def c1 (mk-chasseur :couteau))(def c2 (mk-chasseur :fusil))

Page 57: Mix it 2011 - Clojure

Polymorphisme non intrusif

(defn croise [x y] (condp = [(:espece x) (:espece y)] [:lapin :chasseur] :fuit [:lapin :lapin] :accouple ;TODO [:chasseur :chasseur] :trinque [:chasseur :lapin] :tue))

Version 1: fonction simple, combinaison d'espèces combinables close

Page 58: Mix it 2011 - Clojure

Polymorphisme non intrusif

(defmulti croise (fn [x y] [(:espece x) (:espece y)]))(defmethod croise [:lapin :chasseur] [l c] :fuit)(defmethod croise [:lapin :lapin] ;TODO [l1 l2] :accouple)(defmethod croise [:chasseur :chasseur] [c1 c2] :trinque)(defmethod croise [:chasseur :lapin] [c l] :tue)

Version 2: Multiméthodes = dispatch en fonction des arguments = "héritage simple on steroids"

Page 59: Mix it 2011 - Clojure

Polymorphisme non intrusif

user=> (croise l1 l2):accoupleuser=> (croise c1 c2):trinqueuser=> (croise c1 l1):tueuser=> (croise l1 c1):fuit

Fait remarquable :Code appelant identique dans les 2 cas

Page 60: Mix it 2011 - Clojure

Polymorphisme non intrusif

Fait remarquable 2 :multiméthodes extensibles et redefinablesintroduire :lapine héritant de :lapinredéfinir croise pour [:lapin :lapin] définir croise pour [:lapin :lapine]

Page 61: Mix it 2011 - Clojure

Gestion des états

Page 62: Mix it 2011 - Clojure
Page 63: Mix it 2011 - Clojure

Mutable stateful objects are the new spaghetti code:

Hard to understand, test, reason aboutConcurrency disaster

Page 64: Mix it 2011 - Clojure

Concurrentthreadsafe

managéGC-like

Page 65: Mix it 2011 - Clojure

Gestion saine des états

Pas que pour le multithreadEtat : ensemble des valeurs prises par toutes les variables d'un système à un instant donné

trop de variables en OO classiquedifficulté à raisonner sur le système

Confusion entre :valeuridentité

Page 66: Mix it 2011 - Clojure

Confusion valeur identité

Qui n'a jamais douté d'une lib tierce ?A-t-elle garder une référence sur mon objet ?Cet objet est-il la valeur présente ou est-il mis à jour en continu ?

Ruine la programmation par valeurs

Page 67: Mix it 2011 - Clojure

Confusion valeur identité

Map container = new HashMap();Map a = new HashMap() { {this.put("a", 1); this.put("b", 2);}};Map b = new HashMap() { {this.put("a", 1); this.put("b", 3);}};container.put(a, "bientôt introuvable");a.put("b", 3); // System.out.println(container.get(a));// nullSystem.out.println(container.get(b));// nullSystem.out.println(container);// {{b=3, a=1}=bientôt inaccessible}

Page 68: Mix it 2011 - Clojure

Confusion valeur identité

Question clé :Cet objet est-il la valeur présente ou est-il mis à jour par ailleurs ? Pas de confusion sur les primitives

car immuables Cas d'école : JodaTime vs j.u.Calendar

immuable = tranquilité d'espritimmuable = threadsafe

Incrédules ? Lisez JCIP !

Page 69: Mix it 2011 - Clojure

Confusion valeur/identité

Faut que ça change !Tout est immuableSauf les références

Une référence est juste une boîtecontient une valeurdont le contenu peut changer

Aucune ambiguité Soit c'est une identité (référence)Soit c'est une valeurCode plus clairMoins de code défensif(je vais me faire une copie au cas où)

Page 70: Mix it 2011 - Clojure

Le cadeau Bonux

Références en tant qu'objets first class

passables en paramètreou valeurs de retouretc.

peuvent imposer une logiqueDéjà vu ?

java.lang.ref.* pardi !

Page 71: Mix it 2011 - Clojure

Le cadeau Bonux

java.lang.ref.*ajoute une logique spécifique pour lagestion mémoire

Références Clojureajoutent une logique spécifique pour lagestion de la concurrence !

Plusieurs types de référencesles refs, pour les màj transactionnellesles atoms, pour les màj isoléesles agents, pour les màj isolées et asynchroneschaque type est un pattern de coordination

Page 72: Mix it 2011 - Clojure

Le cadeau Bonux

Modèle unifié @ ou deref pour liretoujours la même signature pour lesfns de mise à jour

(def une-ref (ref 39))(def un-agent (agent 21))(def un-atom (atom 63)) [@une-ref @un-agent @un-atom]; [39 21 63] (alter une-ref + 3)(send un-agent * 2)(swap! un-atom * 2/3)

Page 73: Mix it 2011 - Clojure

Y en a un peu plus, je vous le mets quand même ?

Page 74: Mix it 2011 - Clojure

Pour en savoir plus

clojure.orgdisclojure.org – excellent daily digest quelques librairies : ring, incanter, compojure, enlivele channel irc #clojure et le google grouples multiples livres :

Programming Clojure (daté)Practical ClojureJoy of Clojure (érudit)et Clojure Programming (bientôt en rough cut)d'autres en préparation : Programming Clojure 2nded, Clojure in Action, Meeting Clojure etc.

Page 75: Mix it 2011 - Clojure

Questions

Page 76: Mix it 2011 - Clojure

(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))

VRAIMENT ? Voyez plutôt ...

Page 77: Mix it 2011 - Clojure

(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))Java : obj.getClient().getAdresse().getZipCode() => ()()()

Clojure :(-> obj .getClient .getAdresse .getZipCode) => ()

Page 78: Mix it 2011 - Clojure

(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))Java :if ( nullable != null ) { ... foo ...} else { ... bar ...} => () {} {}

Clojure :(if nullable ... foo ... ... bar ...) => ()

Page 79: Mix it 2011 - Clojure

Nous pouvons le reconstruire,Nous en avons la possibilité technique

Orienté Objet