Programmation agile et sans complexité accidentelle avec Clojure

publicité
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
Téléchargement