Mix it 2011 - Clojure
-
Upload
lolopetit -
Category
Technology
-
view
2.159 -
download
2
description
Transcript of Mix it 2011 - Clojure
Clojure(oubliez vos préjugés)
Christophe Grand @cgrandLaurent Petit @petitlaurent
Mix-it 2011, 5 avril
Motivations
MotivationsRéduire complexité accidentelle
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"
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)
Simple fiable
orthogonalpeu de syntaxe
peu de conceptspeu de surprises
larges abstractions
Réduire la complexité accidentelle
MotivationsRéduire complexité accidentelle Programmation concurrente
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
MotivationsRéduire complexité accidentelle Programmation concurrente
Expressivité maximale
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 !
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();
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)
MotivationsRéduire complexité accidentelle Programmation concurrente
Expressivité maximale Langage généraliste
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)
MotivationsRéduire complexité accidentelle Programmation concurrente
Expressivité maximale Langage généraliste
MotivationsRéduire complexité accidentelle Programmation concurrente
Expressivité maximale Langage généraliste
MotivationsRéduire complexité accidentelle Programmation concurrente
Expressivité maximale Langage généraliste
Caractéristiques
Caractéristiques
Base fonctionnelle
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
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
Caractéristiques
Base fonctionnelle
Etats Managés
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
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
Caractéristiques
Base fonctionnelle
Etats Managés
Lisp
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
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" !
Caractéristiques
Base fonctionnelle
Etats Managés
Lisp
JVM
JVM
JVMpragmatismeécosystèmeentreprise
Compiléinteractif ou AOT
*warn-on-reflection*
PerformantJava en benchmark
Hotspot-friendly
InteropZéro overhead java.util.* Runnable & Callable
JVM
Caractéristiques
Base fonctionnelle
Etats Managés
Lisp
JVM
Syntaxe
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"))
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"))
Fonctions
Fonctions "anonymes" : définitions
Explicite :(fn [args] ...code...)
Fonctions "anonymes" : définitions
Explicite :(fn [args] ...code...) Contractée 1 argument :#(foo (bar %) (baz %))
Fonctions "anonymes" : définitions
Explicite :(fn [args] ...code...) Contractée 1 argument :#(foo (bar %) (baz %)) Contractée n arguments :#(foo (bar %1) (baz %2))
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))
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)
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
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
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
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))
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
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]]
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
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}}
Orthogonalité
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
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]
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
Orthogonalité : lazy sequences
Accès uniforme aux champs
Polymorphisme non intrusif
Clear upgrade path
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
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))
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
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"
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
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]
Gestion des états
Mutable stateful objects are the new spaghetti code:
Hard to understand, test, reason aboutConcurrency disaster
Concurrentthreadsafe
managéGC-like
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é
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
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}
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 !
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ù)
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 !
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
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)
Y en a un peu plus, je vous le mets quand même ?
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.
Questions
(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))
VRAIMENT ? Voyez plutôt ...
(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))Java : obj.getClient().getAdresse().getZipCode() => ()()()
Clojure :(-> obj .getClient .getAdresse .getZipCode) => ()
(let [prejuge (= LISP "Lots of Irritating Superfluous Parentheses")] (not= Clojure prejuge))Java :if ( nullable != null ) { ... foo ...} else { ... bar ...} => () {} {}
Clojure :(if nullable ... foo ... ... bar ...) => ()
Nous pouvons le reconstruire,Nous en avons la possibilité technique
Orienté Objet