Programmation agile et sans complexité accidentelle avec Clojure Rafik Naccache [email protected] 1/91 Le programme ... ● La complexité accidentelle ● La philosophie Clojure ● Programmation Fonctionnelle ● Programmation Concurrente ● Maîtriser la complexité de son programme ● Interopérabilité Java ● Écosystème Clojure ● Histoires Intéressantes... ● Clojure en Tunisie...Pourquoi ? 2/91 Mais avant de commencer... Clojure c'est quoi au juste ? From clojure.org : «Clojure is a dialect of Lisp, and shares with Lisp the code-as-data philosophy and a powerful macro system. Clojure is predominantly a functional programming language, and features a rich set of immutable, persistent data structures. When mutable state is needed, Clojure offers a software transactional memory system and reactive Agent system that ensure clean, correct, multithreaded designs. » 3/91 Donc plus simplement Clojure c'est ● ● ● ● ● Un langage de la famille des langages Lisp (une bonne chose, si, si!) : élégant, expressif et sans parasites. Qui est en symbiose avec des plateformes hôtes Prouvées (JVM, JavaScript,CLR ) Qui est à typage dynamique, compilé à la volée en Java Bytecode ( non interprété en code Java) Un langage fonctionnel ( une bonne chose aussi) Adapté pour la programmation concurrentielle, grâce à une modélisation séparant valeurs, identités, état et temps 4/91 Mais plus précisément, Clojure est intéressant pour deux choses : ● ● La simplicité : Clojure permet de faire les choses directement, sans complexité accidentelle. La puissance : L'aptitude de produire et déployer sans peine nos programmes – grâce notamment à une interop. Java Transparente. 5/91 Et pourquoi Lisp déjà ? ● Syntaxe élégante (… inexistante !) ● Code is data ● Primitives de manipulation de listes ● C'est différent, ça vient d'une autre planète ... ● … Et ce n'est pas de la théorie ! (Paul Graham, ITA) 6/91 Et pourquoi faire l'investissement de l'apprendre ? ● ● ● ● Pour le Fun. Pour coder avec le minimum de complexité accidentelle. Pour Jouir d'un environnement de développement agile pour la JVM (prototyping, Test Driven Development avec des REPL) Pour programmer en mode concurrent sans peine. 7/91 Et où est le piège ? Ça doit bien coûter quelque chose non ? ● ● C'est « un peu » différent... Il y a un investissement en temps et un effort à déployer pour l'apprendre... … Mais ça en vaut vraiment la peine 8/91 Complexité Accidentelle « Pourquoi faire compliqué quand on n'en a pas vraiment besoin » ? 9/91 La complexité accidentelle (1) Trop d' « Objet » tue l'objet ● On a tendance à trop voir le monde en objets – Table , pomme, fenêtre, répertoire : Ok – Controller ?, MetaMegaObjectFacory.Runnable?, java.lang.Math ? (Kingdom of nouns – Yegge 2006) ● ● Couplage fort entre les objets et les traitements – Voiture.rouler(5) : Ok – Java.lang.Math.Random(3) ???? Tendance à avoir du Code « Bavard » et « complexe »( Badd 1995) 10/91 Complexité accidentelle (2) Mais que contient ma variable ? ● ● ● L'Etat : Que contient X maintenant ? L'Identité : Que représente X ? Trop souvent, ces deux notions sont confondues => variables mutables 11/91 Complexité accidentelle (3) J'ai un « problème d'expression » ● ● Le problème d'expression désigne la situation où l'on désire qu'une classe que l'on ne contrôle pas (compilée) implémente une interface. Les solutions comme le monkey-patching (ajout de méthodes à la volée ...) ou la création d'un objet intermédiaire ajoutent de la complexité. 12/91 Complexité accidentelle (4) C'était quoi la syntaxe déjà ? ● ● Il faut souvent maîtriser une syntaxe assez variée pour maîtriser un langage : – Mots clé – Types (pour les langages statiques) – Grammaire variée : opérateur, classe, structures de contrôle, cast, pointeurs,.... – Précédence d'opérateurs Aller retour sur Google ou... BoilerPlate ! 13/91 Complexité accidentelle (5) ça serait bien si mon langage... ● Parfois on rêve que des mots clé nouveaux, de nouvelles structures de contrôle puissent exister dans notre langage préféré : unless (cond) { obj.action() ;} Malheureusement, on doit s'en tenir à (if !cond) ● Il est très difficile de créer des DSL (Domain Specific Language) 14/91 Complexité accidentelle (6) cpt, i, j, k, aux, ... ● Le paradigme impératif nous oblige à utiliser des variables qui n'ont RIEN A VOIR AVEC NOTRE PROBLEMATIQUE : for (int i=0; i<5;i++) { for (int j=0; j<5; j++) { System.out.println (t[i]*[j]); } } 15/91 Complexité accidentelle (7) Spaghetti de mutex... ● ● L'art de gérer la section critique par des mutex a donné naissance à toute une science... … qui n'a pas de lien direct avec notre programme ! 16/91 La philosophie Clojure «Un joyau de splendeur mathématique et de beauté intellectuelle austère (à propos de Lisp)»Gregory Chatain 17/91 Philosophie Clojure (1) La programmation interactive ● ● ● Un programme Clojure est compilé « à la volée » - forme par forme - en Byte Code Java La REPL (Read Eval Print Loop) est le mode privilégié de programmation. Possibilité de voir le rendu de classes JAVA en expérimentant. 18/91 Philosophie Clojure (2) L'héritage Lisp – Les S-Expressions. C'est : ● Des listes imbriquées, ● Pour le code et les données, ● Avec notation préfixée, ● ● Introduites par John Mc Carthy pour définir la famille des langages Lisp, Au fait, 99 % de syntaxe dans un prog. Lisp 19/91 Philosophie Clojure (3) C'est à peu près ça... (fonction param1 param2 param3...) (+ 1 3) ;4 (println « hello ») ;hello ( if ( < 1 3) (println « Toujours vrai ») (println « ça ne doit pas passer... »)) ;Toujours vrai 20/91 Philosophie Clojure (4) Langage fonctionnel pragmatique ● ● ● Les fonctions sont « des citoyens de premier ordre » Les données sont principalement immuables Les fonctions sont pures – pas de changement d'état, et elles sont indépendantes du contexte. 21/91 Le paradigme fonctionnel existait avant (Haskell,...) mais était trop « loin de la réalité ». Pourquoi s'en soucier maintenant avec Clojure ? ● ● ● ● Les architectures MultiCoeurs sont la norme, et l'approche fonctionnelle en tire profit Clojure permet de bien gérer les changements d'état quand c'est nécessaire Clojure est à Typage dynamique et est agile, L'interop. Java est transparente et reste dans la logique classique de l'OO impératif mutable. 22/91 En étant fonctionnel, on gagne quoi par rapport à l'impératif ? ● On est plus simple : pas de boucles, pas de compteurs, pas d'altération d'états ● C'est Thread-Safe ● C'est parallélisable 23/91 Philosophie Clojure (5) Unification des data en Seq(uence) ● Les programmes manipulent une grande variété de Structures de données : – ● Listes, vecteurs, maps, sets, trees, Classes, String, Ints,... Clojure Permet d'unifier toutes ces structures sous l'abstraction Seq 24/91 Philosophie Clojure (5') Les Collections (Seq-able) ● Est une Seq-(able) : – Toutes les collections Clojure – Toutes les collections Java – Tous les tableaux et Strings Java – Les résultats RegEx – Les Structures de répertoires – Les Streams I/O – Arbres XML (JSON aussi via librairies tierces) 25/91 Philosophie Clojure (5' Bis) Survol Rapide des Seq ● Support de l'api Seq : first, rest, next ● Vectors : [ a b c] ● Maps : {:id 1 :name 2 :lastname 3} ● Set : #{[1 2] [3 2]} ● ... 26/91 Philosophie Clojure (...5) Opérations sur les Seq ● ● Construction : cons, conj, ... Transformation : les cèlèbres map et reduce (map * [ 1 2 3] [3 2 1] );(3 6 3) (reduce + [ 1 2 3 ● ● 4 ]) ; 10 Filtrage :(filter even ? [ 1 2 3]) Fonctions spécifiques / Type (algèbre relationnelle sur les sets...) 27/91 Philosophie Clojure (V) Seq infinies et paresseuses (lazy) ● Les éléments d'une seq ne sont évaluées que si on en a besoin : – Optimisation RAM,IO, Temps (def x (range 1 100)) ● Certaines Seq peuvent être infinies (ex : les nombres impairs, premiers,...) (take 10 (iterate inc 1)) 28/91 Philosophie Clojure (5?) Seq-ing Java (re-seq #''\w+ '' « hello world ») ;(« hello » « word ») (map #(.toUpperCase %) « hello world ») ) ; HELLO WORLD 29/91 (re-seq #''\w+ '' Philosophie Clojure (5...) Les seqs. Quelles garanties de performance ? ● ● Garantie que l'ajout et l'accès d'éléments se passent en temps constant. … Et les seqs sont persistantes – on en dira plus après ! 30/91 Philosophie Clojure (6) Il ne faut pas verrouiller... ● Problèmes de la section critique : – Quoi verrouiller ? – Quand verrouiller ? – Quand déverrouiller ? – Interblocage ? – Performance ? – Ce qu'on verrouille doit vraiment l'être ? – Rapport avec notre application ? 31/91 Programmation fonctionnelle 32/91 Programmation Fonctionnelle(1) Où les fonctions sont pures... ● ● ● Les fonctions sont pures si : Elles ne dépendent que de leurs arguments pour déterminer les valeurs de retour... Et ne changent rien au monde extérieur. 33/91 Programmation Fonctionnelle(2) … Et qui vont avec les valeurs immuables (defn blackbox [input] ( (if input data-1 data-2) ) Pour que blackbox soit pure, il faut que data-1 et data-2 ne changent pas au cours du temps ! 34/91 Programmation Fonctionnelle (3) Les Structures de données immuables ● Clojure dépend des S.D immuables : ● Pour l'aspect fonctions pures ● Pour l'aspect état dans un contexte concurrentiel 35/91 Programmation fonctionnelle (4) Prix de l'immuable ? ● ● ● Performance ? S'il faut garder une copie à chaque changement, la RAM va être vite débordée. Clojure ne fait pas de copies simples.Il utilise des S.D persistantes. Il partage efficacement la structure entre les versions des DATA 36/91 Programmation fonctionnelle (5) Les Structures Persistantes (def a '(1 2)) ;#'user/a (def b (cons 0 a)) ;#'user/b 37/91 0 1 b a 2 Programmation fonctionnelle (6) « Paresse » ● ● Les fonctions dans Clojure ne sont pas « paresseuses », mais utilisant les Seq la plupart du temps, on aboutira à du code « Lazy ». La « paresse » n'a de sens qu'en contexte fonctionnel pur : – Indépendance du « quand » d'exécution 38/91 Programmation fonctionnelle (7) Transparence Référentielle ● ● ● Ou « les fonctions peuvent être remplacées par leurs résultat en toute sécurité» Permet la « memoization » et la parallélisation Les fonctions pures le sont Construction. 39/91 par Programmation fonctionnelle (8) Un peu de récursivité sur la JVM ● ● La récursivité, en absence de « tail call optimization » (sur la JVM), est très dangereuse !!! Clojure fournit des appels pour enclencher spécifiquement une « récursivité terminale » 40/91 Programmation fonctionnelle (9) recur (defn et/ou loop) (defn recursive-fact [n acc] ( if ( = n 1) acc (recur (- n 1) (* acc n)) ) ) 41/91 Programmation Fonctionnelle (10) Récursivité mutuelle ● ● ● Support de la récursivité mutuelle avec trampoline Si deux fonctions s'appellent mutuellement, lancer le premier traitement avec trampoline optimise la récursivité mutuelle. Les fonctions doivent retourner des fonctions pour les appeler avec trampoline 42/91 Programmation Fonctionnelle (11) Trampoline en action (Declare my-odd? my-even?) (defn my-odd? [n] ( if (= n 0) false #(my-even? (dec n ) ))) (defn my-even? [n] ( if (= n 0) true #(my-odd? (dec n ) ))) L'appel se fait par : (trampoline my-even? 1000000) 43/91 Programmation Fonctionnelle (12) La « memoization » ● La « memoization » permet de stocker le résultat de fonctions pures dans la RAM. (def memo-ver (memoize une-fonction ) 44/91 Programmation concurrente « Transactions, Agents et Atomes » 45/91 État, valeur, identité, temps... ● ● ● Un état est une valeur prise par une identité à un certain temps donné... Une valeur est une S.D immuable et persistante au cours du temps. Dans le cadre de fonctions pures, on manipule des valeurs. 46/91 Une identité doit prendre plusieurs valeurs au cours du temps, non ? ● L'écoulement du temps rend les choses complexes : une identité prend nécessairement plusieurs valeurs, => mais ce changement n'efface pas les anciennes valeurs que cette identité a prises. Exemple : L'historique d'une équipe de foot 47/91 Bon, essayons de résumer. ● ● ● ● Le temps évolue. C'est la vie. Une valeur est une grandeur immuable et persistante au cours du temps. Une identité peut prendre plusieurs valeurs au cours du temps. Et l'état d'une identité, c'est la valeur qu'elle prend à un point donné dans le temps. 48/91 Clojure sépare clairement les notions de valeur, d'identité et d'état ● ● Par défaut, tout est valeur dans Clojure. Quand la notion d'identité et d'états sont nécessaires, Clojure fournit des API pour les gérer d'une manière « Thread-Safe ». 49/91 Concurrence et parallélisme ● ● La concurrence implique plusieurs processus opérant simultanément Le parallélisme implique la division de lots de travaux séquentiels sur plusieurs parties pouvant être exécutées simultanément. 50/91 Concurrence, parallélisme et l'histoire de l'identité et des valeurs ● ● ● Ils impliquent forcément que plusieurs observateurs auront accès à des données partagés. Ce qui est un problème quand le langage confond identité et valeur. Chaque opération écrase l'historique de l'identité, compromettant la travail de tous les autres processus y ayant accès. 51/91 Donc le verrouillage s'impose ... ● ● ● Dans les langages confondant identité et valeur, où les données sont donc mutables, On utilise les verrous, sémaphores, mutex, … Comme des gardiens : – Ils contrôlent votre accès à une ressource – Et s'assurent que personne d'autre n'y a accès entre-temps 52/91 Et le problème avec les verrous c'est ... ● La performance : On bloque tous les processus sauf un, pour être sûr que tout va bien... =>Peut être que ce n'est pas tout le temps efficace ● L'implémentation et la complexité accidentelle : => Il faut savoir où et quand mettre ces verrous => Savoir quand les relâcher... => Rien à voir avec le champ de votre application 53/91 Clojure, les Refs et la Software Transactional Memory ● ● La plupart des objets dans Clojure sont immuables. Quand vous avez besoin de gérer un état, vous devez explicitement créer une ref. 54/91 Clojure, les Refs et la Software Transactional Memory (2) – créer une ref Par exemple, pour créer une ref vers la chanson courante dans une playlist : (def current-track (ref « track-01 »)) Pour lire le contenu de la ref, on peut appeler deref ou @ : (deref current-track) ; ou (@current-track) Ici la chanson(valeur) est immuable, mais l'identité current-track(mutable) peut changer d'état en prenant plusieurs valeurs (les chansons) 55/91 Clojure, la Software Transactional Memory (enfin) ! ● ● Pour changer la valeur référencée par une ref, il suffit de lancer ref-set. Mais … ref-set doit être protégée dans une transaction (software Transactional Memory) 56/91 La STM, mais c'est la transaction des bases de données ? C'est ACID ? ● ● ● ● Elles ne sont pad D(urable) : C'est dans la RAM, enfin... Elles sont Atomic : plusieurs refs manipulées : tout ou rien. Consistent : Les refs peuvent spécifier des fonctions de validation qui peuvent faire échouer la transaction. Et Isolated : Les transactions en cours ne voient pas le résultat d'autres transactions non totalement validées. 57/91 Donc pour changer notre chanson courante et son compositer, en une STM dans Clojure... (def current-track (ref « track-01 »)) (def current-composer (ref « band-01 »)) (dosync (ref-set current-track « track-02 ») (ref-set current-composer « band-02 ») ) 58/91 Comment marche la STM : La MVCC (MultiVersion Concurrency Control) ● ● ● ● Mécanisme utilisé dans les bases de données. La transaction prend une copie (voir la persistance) de la valeur de la réf à un point nommé. Si la STM détecte que la valeur de cette ref a changé, la transaction est redémarrée à son point de départ. Si la transaction est validée, la valeur de la ref véhiculée par celle-ci est considérée comme le nouvel état global 59/91 Derniers mots sur la STM Clojure ● ● Il existe des primitives pour appliquer des fonctions de modification : alter, commute (moins restrictive) On peut appliquer des fonctions de validation sur les ref, lors de leur création. 60/91 Les atom pour une seule mise à jour à une seule ressource ● ● ● Plus légers que les ref,les atoms permettent de gérer les accès concurrents à une seule valeur. Il s'agit d'une seule AàJ, on ne parle plus de transactions. Reset ! Pour Modifier, swap ! Pour appliquer une fonction 61/91 Notre playlist serait plus simple avec les atom ● (def current-track (atom {:title « t1 » :composer « c1 »} ) ) ● (swap! Current-track assoc :title « t2 » :composer « c2 ») 62/91 Les agents Clojure pour des mises à jours asynchrones ( différées) ● Les Agents Clojure permettent d'exécuter des tâches différées, qui ne nécessitent pas de coordination avec d'autres tâches. (def counter (agent 0) (send counter (inc)) @counter ; 1 ● Les agents permettent aussi d'avoir des fonctions de validation et de gestion d'erreurs. 63/91 Maîtriser la complexité « Il n'est pas facile de trouver des solutions simples à nos problèmes... » 64/91 Des abstractions pour passer tout partout ... ● ● Il est utile de définir des interfaces, pour pouvoir étendre nos programmes à de possibles évolutions. Par exemple, en créant l'interface reader et writer, on a pu ajouter url et socket à nos streams de lecture/écriture. 65/91 La liberté d'expression, quoi ! ● ● Une interface est donc le moyen de spécifier des méthodes abstraites, et des classes devraient les implémenter pour en profiter. Le paramètre déterminant l'implémentation spécifique à utiliser dans chaque cas est l'objet implémentant l'interface. 66/91 … Le problème d'expression ? ● ● ● ● Le problème d'expression vient : Quand un on veut qu'un objet qu'on ne contrôle pas... Implémente une interface ! Solutions : classe intermédiaire, monkey-patching, recompilation de notre version...COMPLEXITE COMPLEXITE COMPLEXITE! 67/91 Les Protocol, une solution au problème d'expression...même pour les classes Java ! ● ● Le protocol reprend l'esprit de l'abstraction derrière les interfaces, Avec L'avantage que plusieurs datatypes (et donc même classes java) peuvent l'étendre à n'importe quel moment du programme. 68/91 Protocoles - exemple (defprotocol IOFactory (make-reader [x]... (make-writer [x]... (extend ma-structure / Ou ma-classe-java (IOFactory (make-reader[x] (implementation... (make-writer[x] (implementation... 69/91 Multimethods ● ● Classiquement, en POO, l'implémentation d'une méthode dépend du type de données de son appelant. En Clojure, l'implémentation à utiliser dépend d'une fonction de dispatching, offrant plus de souplesse... 70/91 Multimethods (2) (defmulti mon-type even? ) Fonction de Dispatching (defmethod mon-type true (println « je suis pair »)) (defmethod mon-type false Selon le résultat, implémentation (println « je suis impair »)) 71/91 Macros – Où comment les programmes s'écrivent eux même... ● ● ● Clojure hérite du puissant système de macros Lisp. Leur grande puissance vient du fait que le code et les données ne font qu'un ! En écrivant des macros, on ajoute des fonctionnalités au langage ! 72/91 Macros – Où comment les programmes s'écrivent eux même... ● ● On peut les utiliser pour implémenter de nouvelles structures de contrôles, des DSL... Mais il ne faut pas en abuser! 99 % des macros peuvent être implémentées par des fonctions 73/91 Macros – Où comment les programmes s'écrivent eux même... ● Fonction unless : (defn unless[expr form] (if expr nil form) ● Macro unless : (defmacro unless [expr form] (list `if expr nil form) 74/91 Interopérabilité Java « write once, debug everywhere » 75/91 Appeler Java dans Clojure ● (new java.util.Random) ● (def rnd (new java.util.Random)) ● (. rnd nextInt) ou (.nextInt rnd) ● (.. class1 trait1 trait2 trait3) 76/91 Les grands nombres ● ● Les opérateurs de base Java ne traitent pas convenablement les cas d'overflow. Existence de 3 types d'opérateurs : Unchecked Default Promoting (java Prmitives) (exception) (use big int) unchecked-add 77/91 + +' Exception Checking made simple... ● La gestion des exceptions Java est fastidieuse : – Obligation de gérer toutes les « checked exceptions » – Utiliser Finally pour correctement femer des ressources physiques (fichiers, réseaux) – Faire face au problème : ignorer l'exception, réessayer le problème... 78/91 Exception Checking made simple... ● Dans Clojure, les choses sont simplifiées : – On n'est pas obligé de traiter toutes les « checked exceptions » – Existence de macros comme « withopen » pour éviter de devoir libérer manuellement les ressources. 79/91 Clojure, améliorer le traitement des exceptions ● Version not caller friendly : Version caller friendly : (defn class-available? [class-name] (defn class-available? [class-name] (Class/forName class-name) (try (Class/forName class-name) true ; sort une exception .classNotFoundException 80/91 (catch ClassNotFoundException _ false))) ; sort true or false Écosystème Clojure #clojure 81/91 Leiningen – On aurait du le mettre à la place de la homepage de clojure.org ● Il Sert à : ● Installer Clojure ! Si Si ● Créer des projets ● Packager des projets ● ● résoudre les dépendances (Clojure et Java) Lancer des REPL, des Tests, ... 82/91 Ecosystème - IDE ● JVM + Leiningen sont (presque) les seuls prérequis, après, chacun son arme : ● Emacs + cider (ex nrepl.el ) ● Eclipse + Counter Clock Wise ● Intellij Idea + La Clojure ou Cursive ● Vim + FirePlace ● Clooj, Light Table, ... 83/91 La communauté est hyper active... ● ● ● Clojars et ClojureWerkz #clojure sur Freenode , Clojure sur Google +, sur googlegroups. Les résultats de la Clojure Survey 2013 sont là ! 84/91 Histoires Intéressantes... 85/91 Ambrose et sa campagne de Financement pour core.typed http://www.indiegogo.com/projects/typed-clojure ● ● ● C'est un jeune qui a écrit une Librairie, core.typed, en tant que Projet de Fin d'Etudes. Il a lancé une campagne pour lever 20 000$ pour continuer à le faire à temps plein. Il a levé 35 000 $ ! 86/91 Expérience Netflix https://speakerdeck.com/daveray/clojure-at-netflix ● ● ● L'expérience d'une équipe RnD à Netflix qui Teste Clojure. Ils ont été encouragé par l'interop. Java, très présent à Netflix. Leur conclusion : Clojure peut produire des gains de productivité et de satisfaction, mais il faut prendre son temps et saisir les différences. 87/91 Backtype / Cascalog www.cascalog.org ● ● Backtype a créé un produit cascalog pour requếter facilement des données de réseaux sociaux (à base de Hadoop) Backtype a été acquis par twitter !! 88/91 Une communauté clojure en Tunisie... Pourquoi ? 89/91 Clojure en Tunisie, Pourquoi ? ● ● ● ● Créer un centre de compétences de qualité (et rares) Améliorer la qualité des logiciels produits dans notre pays. Créer des produits à haute valeur ajoutée, se différencier Pour le fun, encore une fois ! 90/91 Merci, des Questions ? Rafik Naccache [email protected] 91/91