“Tout ce que vous aveztoujours voulu savoir sur la
programmation fonctionnelle**sans jamais oser le demander”
François Sarradin
Xebia ITArchitects
● "Bien pour la programmation concurrente" ● "Moins de bugs" ● To reduce cyclomatic complexity is far more important than to
reduce the number of LOCs. Incidentally FP gives a great help to lower both -- @mariofusco
● "Un autre point de vue sur son langage quotidien" ● "Ça retourne le cerveau, pour le bien !"
Programmation fonctionnelleCe qu'on en dit...
○ "Ellitiste" ○ "Difficile à apprendre" ○ "À ne pas mettre entre toutes les mains" ○ "Purement académique" ○ "Ça retourne le cerveau"
Programmation fonctionnelleCe qu'on en dit aussi...
OK, mais...
Qu'est-ce que laprogrammation fonctionnelle ?
Récursion
Fonction
Monade
Monoïde
Lazy evaluation
Tail recursion
Curryfication
Point freePattern matching
ProgrammationFonctionnelle
Combinateur de point fixe
Continuation
Closuremap/filter/fold/zip
Lambda calcul
Inférence de type
Catamorphisme
Type system
Functor
Ordre supérieur
Type algébrique
Transparence référentielle
Au programme...
● La base du style fonctionnel○ Histoire de la programmation fonctionnelle○ Récursion / Ordre supérieur / Déterminisme○ Optimisation
● Traitement de flux d'informations
○ Liste en programmation fonctionnelle○ Fonctions sur listes
● Conclusion
● Hands On
● Haskell (principalement) ● Scala, Clojure, Erlang, etc.
● Java + Guava (pour le Hands On)
Langages utilisés
Commençons
Soit la fonction factorielle
"n! est le produit des entiers compris entre 1 et n"
ou
n! = 1 * ⋯ * n = Πi i , ∀ i ∈ [1..n]
ou
0! = 1,n! = n . (n - 1)!, si n > 0.
StyleFonctionnel
int fact(int n): result = 1 for i in [1..n]: result = result * i return result
fact n = product [1..n](haskell)
Impératif
(defn fact [n] (reduce * 1 (range 1 (inc n))))
(clojure)
fact(0) -> 1;fact(N) -> N * fact(N - 1).
(erlang)
public int fact(int n) { if (n == 0) return 1; else return n * fact(n-1);}
(java)
Et aussi...
Point freeComposition de fonctions + disparition des variables ContinuationFutur de la fonction en paramètre Combinateur de point fixePermet les fonctions anonymes et récursives MonadeChaînage de traitements
Mais aussi des dérives...
factorial←{⍺←1⍵=0:⍺(⍺×⍵)∇ ⍵-1
}
life←{ ↑1 ⍵∨.^3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵ }
APL (A Programming Language)
Programmation fonctionnellePrincipe n°1 : tout est fonction
Tout est fonctionAu sens mathématique
f(x) = 2.x
x ↦ x + 42
h(x, y) = ∂2 p(x, y) / ∂x2
FonctionDans les langages fonctionnels
Haskellidentity x = x\x -> x
Scaladef identity(x: Any) = x{ x: Any => x }
Clojure(defn identity [x] x)(fn [x] x)
FonctionAvec Guava
new Function<T, R>() { @Override public R apply(T x) { return x; }} new Predicate<T>() { @Override public boolean apply(T x) { return x == null; }}
Programmation fonctionnelleD'où est-ce que ça vient ?
Fonction mathématiquefin du XVIIe siècle
f : x ↦ x f : x ↦ 2 . x
ou
f(x) = 2 . xGottfried Wilhelm von Leibniz(1646-1716)
λx.x(λx.2*x) 7 → 2 * 7 true ::= λxy.xfalse ::= λxy.yif-then-else ::= λpab.p a b 0 ::= λfx.x1 ::= λfx.f x2 ::= λfx.f (f x)
Lambda-calcul1936
Alonzo Church(1903-1995)
(lambda (x) x)(f arg1 arg2 ...)(lambda (x) (* 2 x)) (cons A B)(car (cons A B)) => A(cdr (cons A B)) => B
LISP1958
John McCarthy (1927-2011)
Et ensuite...
Lisp (1958) Scheme (1975) Common Lisp (1984) Clojure (2007)
APL (1964) FP (1977) J (1990)
ISWIM (1966) ML (1973) Caml (1985) F# (2002) Haskell (1987) Scala (2003)
Forth (1970s) PostScript (1982) RPL (1984) Joy (2001)
Prolog (1972) Erlang (1986)
Version "embarqué"
● Langage○ Java 8 (en 2012 ?)○ Groovy○ Python○ Ruby○ SmallTalk○ ...
● API Java
○ Guava○ LambdaJ○ TotallyLazy○ FunctionalJava○ FunkyJFunctional
Success stories
● Banque / finance○ Jane Street (OCaml)○ Goldman Sachs (Erlang), CitiGroup (Clojure), ...
● Réseaux sociaux○ Twitter (Scala)○ FourSquare (Scala/Lift)○ Facebook (Erlang)
● Télécommunication○ Ericsson (Erlang)
● Autres...○ Microsoft (F#)○ CouchDB, RabbitMQ (Erlang)
Programmation fonctionnellePrincipe n°2 : récursion
Récursion
Boucle = fonction qui s'appelle elle même fact n = if n == 0 then 1 else n * fact (n-1)
Proscrire ○for○while○repeat○ etc.
Verboten !
Stateful !
def fact(n):r = 1for i in [1..n]:
r *= ireturn n
Récursion terminale
L'appel récursif est la dernière instructionfact_t n k = if n == 0 then k else fact_t (n-1) (n*k) fact n = fact_t n 1
Récursion : pile d'appels
-> fact(5) -> fact(4) -> fact(3) -> fact(2) -> fact(1) -> fact(0) <- 1 <- 1 * 1 = 1 <- 2 * 1 = 2 <- 3 * 2 = 6 <- 4 * 6 = 24<- 5 * 24 = 120
fact n = if n == 0 then 1 else n * fact (n-1)
Récursion terminale : pile d'appels
-> fact_t 5 1 -> fact_t 4 5 -> fact_t 3 20 -> fact_t 2 60 -> fact_t 1 120 -> fact_t 0 120 <- 120 <- 120 ...<- 120
fact_t n k = if n == 0 then k else fact_t (n-1) (n*k)
Récursion : beware !
Programmation fonctionnellePrincipe n°3 : ordre supérieur
Fonction d'ordre supérieurFonction en paramètre
Dérivéederiv(f, x) = d f(x) / dt
Application d'une fonction sur chaque élément d'une collection (Java 8 ?)
Arrays.asList(1, 2, 3).map(x -> x * x)
Fonction d'ordre supérieurFonction en sortie
Additionneur (curryfier)add : x ↦ (y ↦ x + y)
add_one = add 1
add_one 2 => 3add_one 10 => 11
Fonction d'ordre supérieur... ou les deux
Compositionf ∘ g : x ↦ f(g(x))
● Généricité / Réutilisation du code● Extension du langage / DSL interne
// Scalanew Order to buy(100 sharesOf "IBM") maxUnitPrice 300 using premiumPricing
new Order to sell(200 bondsOf "Sun") maxUnitPrice 300 using { (qty, unit) => qty * unit - 500 }
Ordre supérieurApports
Programmation fonctionnellePrincipe n°4 : déterminisme
● Fonction = Opération déclarative○ Indépendante + sans état interne + déterministe
Une fonction retourne toujours la même valeur pourvu qu'on lui fournisse les mêmes paramètres => Un bon point pour la programmation concurrente !=> (+) de testabilité, (-) d'effet de bord
Indépendance / Déterminisme
f(x) = x + time() => NON !f(x, t) = x + t => OUI !
x = x + 1 => NON !y = x + 1 => OUI !
● Variable (fonctionnelle)
= variable (mathématique)= constante (impératif)= final (Java)
=> Encore un bon point pour la programmation concurrente !=> (-) d'effet de bord
Immuabilité
Programmation fonctionnelleDéterminisme => Optimisation
Optimisation des fonctions récursives terminales
def fact(n, k): if n == 0: return k return fact(n - 1, k * n)
devient
def fact(n, k): while not (n == 0): k = k * n n = n - 1 return k
OptimisationStyle trampoline
Transparence référentielleUne expression peut être remplacée par son résultat sans changer le comportement du programme MémoizationConserver en cache le résultat d'une fonction selon ses paramètres
Optimisation
Flot de contrôle non linéaireSi aucune dépendance ne les lient, 2 fonctions peuvent être appelées dans n'importe quel ordre Évaluation retardéeÈvaluation selon le besoin Garbage collector-- Since 1958 --
Optimisation
Inférence de typeDéterminer automatiquement le type d'une expression ou d'une fonction Pattern matchingAppliquer un traitement sur une valeur selon son "aspect"=> switch-case on steroid!
value match { // Scala case 1 => ... case "hello" => ... case x:Int => ... case Symbol(a, b) => ...}
Sucre syntaxique
Programmation fonctionnellePrincipe n°5 : liste
Liste de vide[]
Ajouter un élément en tête
1 : [] == [1]1 : [2, 3] == [1, 2, 3]1 : 2 : 3 : [] == [1, 2, 3]
Récupérer la tête
head [1, 2, 3] == 1 Récupérer la queue
tail [1, 2, 3] == [2, 3]
ListeOpérations de base (Haskell)
ListeFinie
Haskell[1,2,3,4,5] => [1, 2, 3, 4, 5][1..5] => [1, 2, 3, 4, 5]['a'..'z'] => "abcdefghijklmnopqrstuvwxyz" Scala(1 to 5) => Range(1, 2, 3, 4, 5)('a' to 'z') => NumericRange(a, b,[...], y, z) Clojure(range 1 5) => (1 2 3 4)
ListeVers l'infini et au delà !
Haskell[1..] => [1, 2, 3, 4, 5, ...tail [1..] => [2, 3, 4, 5, 6, ...take 3 [1..] => [1, 2, 3]take 3 (drop 5 [1..]) => [6, 7, 8] ScalaN/A Clojure(range 1) => (1 2 3 4 5 6 ...
Iterator<Integer> oneToFiveIterator = new AbstractIterator<Integer>() { private int i = 1; @Override protected Integer computeNext() { if (i > 5) return endOfData(); else return i++; } };// évaluation retardée selon Java !
Et en Java + GuavaÉmuler les listes : iterator
Iterable<Integer> oneToFive = new Iterable<Integer>() { @Override public Iterator<Integer> iterator() { return oneToFiveIterator; } }; assertThat(oneToFive)
.containsExactly(1, 2, 3, 4, 5);
Et en Java + GuavaÉmuler les listes : iterable
Et en Java + GuavaListe infinie : suite de 1
Iterator<Integer> onesIterator = new AbstractIterator<Integer>() { @Override protected Integer computeNext() { return 1; }} Iterable<Integer> ones = new Iterable<Integer>() { @Override public Iterator<Integer> iterator() { return onesIterator; }}
Programmation fonctionnelleOpérations de traitement sur listes
mapApplique une transformation sur chaque élément d'une liste
=> Guava : transform(iterable, function)
Haskellmap (+1) [1..5] => [2, 3, 4, 5, 6]
filterConserve que les éléments satisfaisant un prédicat
=> Guava : filter(iterable, predicate)
Haskellfilter (> 3) [1..5] => [4, 5]
Fonction sur listemap et filter
Fonction sur listezip
zipFusionne deux listes
zipWith f [a1, ..., an] [b1, ..., bm] => [f(a1, b1), ..., f(an, bn)]si n < m HaskellzipWith (+) [1..5] [6..8] => [7, 9, 11]
=> Pas d'équivalent en Guava
foldAgrège les valeurs d'une liste (somme, produit, liste, ...)
foldl f a0 [b1, ..., bn]=>
a1 <- f(a0, b1)a2 <- f(a1, b2)...an-1<- f(an-2, bn-1)return f(an-1, bn)
=> Pas d'équivalent en Guava
Fonction sur listefold/reduce
Fonction sur listefold/reduce
Haskellfoldl (+) 0 [1..5] => 15 product l = foldl (*) 1 lfact n = product [1..n] = foldl (*) 1 [1..n] reverse l = foldl (flip (:)) [] lreverse [1..5] => [5, 4, 3, 2, 1]
En résuméLa programmation fonctionnelle, c'est...
Particularité
● Tout est fonction○ Fonction sur des valeurs (1er ordre)○ Fonction sur des fonctions (ordre supérieur)○ Indépendance / Déterminisme / Immuabilité
● Optimisations diverses
● Récursion ● Traitement sur liste
Et plus encore...
Avantages
● Généricité / réutilisation / modularité ● Meilleure testabilité / fiabilité
● Adapter à la programmation concurrente
● Concision
Écrire du code avec un langage fonctionnel
= écrire des spécifications formelles
Difficulté
● Une façon de penser différente
● Courbe d'apprentissage○ idiomes, patterns, best practices
● Longtemps enfermée dans la communauté scientifique ● Déclaratif
○ Pas de maîtrise du déroulement (plus qu'avec Java)○ Bonne connaissance du compilateur/VM
● Trop de concision tue la lisibilité
Littérature
● Miran Lipovača, Learn You a Haskell for Great Good! (LYAH). Avril 2011. http://learnyouahaskell.com/
● Bryan O'Sullivan, Don Stewart, and John Goerzen, Real World Haskell
(RWH). Novembre 2008. http://book.realworldhaskell.org/
Congrès et User Groups
● International Conference on Functional Programming (ICFP), http://www.icfpconference.org/
● Commercial Users of Functional Programming (CUFP), http://cufp.org/
● Scala Days● Clojure/conj
● Paris Scala User Group (soirée - 1/mois)● Clojure Paris User Group (coding dojo - 1/semaine)
Sites webs
● Haskell : http://haskell.org/○ API doc : http://haskell.org/ghc/docs/7.0-latest/html/libraries/index.
html○ Web console : http://tryhaskell.org/
● Scala : http://www.scala-lang.org/○ API doc : http://www.scala-lang.org/api/current/index.html#package○ Web console : http://www.simplyscala.com/
● Clojure : http://clojure.org/○ API doc : http://clojure.github.com/clojure/, http://clojuredocs.org/○ Web console : http://try-clojure.org/