Programmation Fonctionnelle

Post on 30-Jun-2015

2.845 views 2 download

Transcript of Programmation Fonctionnelle

“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/

Hands On : ici...

https://github.com/fsarradin/xke-fp