Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Prog Fonctionnelle 🐑

Prog Fonctionnelle 🐑

en JavaScript, avec un peu de Scala mais pas que
pour le GDG Lille

Philippe CHARRIERE

October 03, 2017
Tweet

More Decks by Philippe CHARRIERE

Other Decks in Programming

Transcript

  1. Philippe Charrière @k33g_org @k33g > Tech evangelist & CSO @Clever_Cloud

    > Core committer https://github.com/eclipse/golo-lang I ❤ JavaScript, I " Java, I JVM, I Golo, I % Scala (depuis peu…) ! 2 — @GDGLille
  2. AGENDA ⚠ il va y avoir du Scala la PF

    et moi quelques bases container, functor, monad(e) Monet.js: Identity, Maybe, Either Autres Monades Union types 3 — @GDGLille
  3. FP AND ME A monad is just a monoid in

    the category of endofunctors, what's the problem? — Philip Wadler 7 — @GDGLille
  4. FP AND ME ⚠ DISCLAIMER AVANT DE CONTINUER Tout le

    monde ne sera pas forcément d'accord L'objectif c'est de commencer à comprendre 2, 3 choses 10 — @GDGLille
  5. MAIS POURQUOI DU JAVASCRIPT > facile à lire si si

    > simple à utiliser ES 2015 > fonctionnel depuis longtemps > encore plus depuis ECMAScript 5 13 — @GDGLille
  6. KEY POINTS > pure function > function as input &

    output > no iteration > (im)mutability 15 — @GDGLille
  7. PURE FUNCTIONS > Pour éviter les effets de bord >

    Toujours retourner le même résultat pour les mêmes paramètres 17 — @GDGLille
  8. NOT PURE FUNCTION var name = "Bob"; function badHey() {

    console.log(`Hello ${name}`); } badHey(); // I'm not sure that `hey()` shows always "Bob" // " can interact with `var name = "Bob";` // `hey()` -> void => can't test 18 — @GDGLille
  9. PURE FUNCTION function goodHey({name}) { // named parameter return `Hello

    ${name}`; } console.log(goodHey({name:"Bob"})); 19 — @GDGLille
  10. CURRYING > Il est possible d'appeler une fonction avec moins

    d'arguments qu'elle n'en attend > Elle retournera une fonction qui attend les arguments restants 21 — @GDGLille
  11. AVANT function youAreSo(name, adjective) { return `${name}, you are so

    ${adjective}` } youAreSo("Bob", " ✨ ") // amazing: Bob, you are so youAreSo("Sam", " "#$ ") // lovely: Sam, you are so 22 — @GDGLille
  12. MAINTENANT AVEC LE CURRYING // a function that returns a

    function that returns a string function youAreSo(adjective) { return function(name) { return `${name}, you are so ${adjective}` } } // "shortcuts" let youAreSoAmazing = youAreSo(" ✨ "); let youAreSoLovely = youAreSo(" "#$ "); youAreSoAmazing("Bob") youAreSoLovely("Sam") 23 — @GDGLille
  13. CURRYING & PF > moins verbeuse > plus agréable >

    ré-utilisation 24 — @GDGLille
  14. NO ITERATIONS Pour faire "plus fonctionnel": > ne pas itérer

    (for, while) pour transformer les données > préférer map, filter, reduce, ... > cf. Java 8 -> streams 27 — @GDGLille
  15. NO ITERATIONS PRÉPARER UN KEBAB: > choisir des ingrédients (sans

    frites, sans oignons, ...) > découper > tout mettre ensemble 29 — @GDGLille
  16. NO ITERATIONS let aliments = [ " ! " ,

    " " " , " # " , " $ " , " % " , " & " // ' pas d'emoji oignons ] 30 — @GDGLille
  17. NO ITERATIONS // actions function decouper(aliment) { return `morceaux de

    ${aliment}`; } // if not frites return true function sansFrite(aliment) { return aliment !== " ! "; } // if not oignon return true function sansOignon(aliment) { return aliment !== " " "; } 31 — @GDGLille
  18. NO ITERATIONS: CHOISIR ET DÉCOUPER // ingrédients pour faire mon

    Kebab let ingredients = aliments .filter(sansFrite) // ⏩ new list .filter(sansOignon) // ⏩ new list .map(decouper) // ⏩ new list (transformer en un autre []) console.log("ingrédients:", ingredients) 32 — @GDGLille
  19. NO ITERATIONS: CHOISIR ET DÉCOUPER NOUVEAU TABLEAU ingrédients: [ 'morceaux

    de ! ', 'morceaux de " ', 'morceaux de # ', 'morceaux de $ ' ] 33 — @GDGLille
  20. NO ITERATIONS: TOUT METTRE ENSEMBLE (À PLAT) // ⚡ combiner(transformer)

    les ingredients let kebab = ingredients.reduce( function(resultatIntermediaire, ingredient) { return resultatIntermediaire + ingredient + " " }, " " avec: " ) console.log(kebab) " " avec: morceaux de # morceaux de morceaux de morceaux de " 34 — @GDGLille
  21. (IM)MUTABILITY aliments[5] = " ! "; // mutability is bad

    let ingredients = aliments.filter(sansFrite).filter(sansOignon).map(decouper) let kebab = ingredients.reduce((resultatIntermediaire, ingredient) => { return resultatIntermediaire + ingredient + " " }, " " avec: ") console.log(kebab) // " avec: morceaux // de # morceaux de $ morceaux de % morceaux de morceaux de // who is the '( who put ! in my " ❓ 36 — @GDGLille
  22. (IM)MUTABILITY > Ne jamais trandformer un tableau (liste, collection, ...)

    directement > utiliser map pour obtenir un nouveau tableau 37 — @GDGLille
  23. (IM)MUTABILITY let bananesAlaPlaceDesOignons = (aliment) => aliment == " !

    " ? " " : aliment let aliments = [' # ', ' $ ', ' % ', ' ', ' ', ' '] aliments.map(bananesAlaPlaceDesOignons) // nouveau tableau // [ ' # ', ' $ ', ' % ', ' & ', ' ' ', ' ' ] 38 — @GDGLille
  24. (IM)MUTABILITY: MES RECETTES PRÉFÉRÉES let myFavoriteRecipe = aliments.filter(sansFrite).filter(sansOignon) .map(decouper) let

    myWeirdReceipe = aliments .map(bananesAlaPlaceDesOignons) // ( new list) .filter(sansFrite) .map(decouper) 39 — @GDGLille
  25. ! EN SCALA val aliments = List(" ! ", "

    " ", " # ", " ", " ", " ") // === action recette === def decouper(aliment: String): String = s"morceaux de $aliment" // === choix aliments === def sansFrite(aliment: String): Boolean = aliment != " " def sansOignon(aliment: String): Boolean = aliment != " " // === filtrer, puis découper === val ingredients = aliments .filter(sansFrite) .filter(sansOignon) .map(decouper) 41 — @GDGLille
  26. ! EN SCALA // === assembler ==== aka reduce aliments

    .filter(sansFrite) .map(decouper) .fold(" ! avec: ") { (tmp, ingredient) => tmp + ingredient + " "} 42 — @GDGLille
  27. WRAPPER ❓ EN JAVASCRIPT class Wrapper { constructor(x) { const

    value = x; Object.defineProperty(this, "value", { get: () => value }) } static of(x) { return new Wrapper(x); } } 45 — @GDGLille
  28. WRAPPER ❓ EN JAVA public class Wrapper<T> { private T

    value; public T getValue() { return this.value; } public Wrapper(T value) { this.value = value; } } 46 — @GDGLille
  29. FUNCTOR class Functor extends Wrapper { constructor(x) { super(x); }

    static of(x) {return new Functor(x);} ! map (fn) { return new this.constructor(fn(this.value)); // return new Functor(fn(this.value)) } } 53 — @GDGLille
  30. FUNCTOR ❓ EN JAVA public class Functor<T> extends Wrapper {

    public Functor(T value) { super(value); } public <R> Functor<R> map(Function<T,R> f) { return new Functor<R>(f.apply((T) this.getValue())); } } 54 — @GDGLille
  31. FUNCTOR > donc un Functor a une méthode map() >

    et les tableaux en JavaScript aussi 55 — @GDGLille
  32. MONAD class Monad extends Functor { constructor(x) { super(x); }

    static of(x) {return new Monad(x);} /* So, I'm a monad because I have a flatMap method */ ! flatMap (fn) { return fn(this.value); } } 60 — @GDGLille
  33. MONAD ❓ EN JAVA public class Monad<T> extends Wrapper {

    public Monad(T value) { super(value); } public <R> Monad<R> map(Function<T,R> f) { return new Monad<R>(f.apply((T) this.getValue())); } public <R> Monad<R> flatMap(Function<T, Monad<R>> f) { return f.apply((T) this.getValue()); } } 61 — @GDGLille
  34. IDENTITY AVEC MONET: MAP const monet = require('monet'); let content

    = monet.Identity("Hello ") let h1 = value => `<h1>${value}</h1>` let body = value => `<body>${value}</body>` let html = value => `<html>${value}</html>` console.log( content.map(h1).map(body).map(html).get() ) //<html><body><h1>Hello ! </h1></body></html> 66 — @GDGLille
  35. IDENTITY AVEC MONET: FLATMAP let content = monet.Identity("Hello ! ")

    let h1 = value => `<h1>${value}</h1>` let bold = value => `<b>${value}</b>` let italic = value => `<i>${value}</i>` // component let title = txt => monet.Identity(txt).map(bold).map(h1) console.log(content.map(title).get()) // { val: '<h1><b>Hello ! </b></h1>' } console.log(content.flatMap(title).get()) // <h1><b>Hello ! </b></h1> 67 — @GDGLille
  36. IDENTITY EN SCALA case class IntMonad(val value: Int) { def

    map(fn: Int => Int) = { val res = fn(this.value); IntMonad(res) // implicit return } def flatMap(fn: Int => IntMonad) = { fn(this.value) } } 69 — @GDGLille
  37. IDENTITY EN SCALA val prixDeDepart = IntMonad(10000) val optionClim =

    (prix: Int) => prix + 2000 val interieurCuir = (prix: Int) => prix + 5000 val vitreTeintees = (prix: Int) => prix + 3000 prixDeDepart .map(optionClim) .map(interieurCuir) .map(vitreTeintees).value 70 — @GDGLille
  38. MAYBE AVEC MONET orSome() const monet = require('monet'); let a

    = monet.Maybe.fromNull(42) // { isValue: true, val: 42 } console.log(a.orSome(0)) // 42 let b = monet.Maybe.fromNull(null) // { isValue: false, val: null } console.log(b.orSome(0)) // 0 76 — @GDGLille
  39. MAYBE AVEC MONET orElse() const monet = require('monet'); let maybe

    = monet.Maybe.fromNull(null) // console.log( maybe.orElse(monet.Maybe.Some(" ! ")) // { isValue: true, val: ' ' } ) let maybeAgain = monet.Maybe.fromNull(" ") console.log( maybeAgain.orElse(monet.Maybe.Some(" ")) // { isValue: true, val: ' ' } ) 77 — @GDGLille
  40. MAYBE AVEC MONET - UTILE - RECHERCHES let getUserById =

    (id) => { let users = [ {id:1, name:"bob", avatar:" "}, {id:2, name:"sam", avatar:" "}, {id:3, name:"jane", avatar:" "}, {id:4, name:"john", avatar:" "} ] return monet.Maybe.fromNull(users.find(u => u.id == id)) } 79 — @GDGLille
  41. MAYBE AVEC MONET - UTILE - RECHERCHES getUserById(2) .orSome(" ⚠

    no user") // { id: 2, name: 'sam', avatar: ' ' } getUserById(7) .orSome(" ⚠ no user") // ⚠ no user 80 — @GDGLille
  42. MAYBE AVEC MONET - UTILE - .map() getUserById(2) .map((user) =>

    user.avatar ) .orSome(" ! ") // " getUserById(7) .map((user) => user.avatar ) .orSome(" ! ") // ! 81 — @GDGLille
  43. CATAMORPHISME "Dans la théorie des catégories, le concept de catamorphisme

    (du Grec: κατα- = vers le bas; morphisme = forme) dénote l'unique homomorphisme pour une algèbre initiale." "En programmation fonctionnelle, un catamorphisme est une généralisation de la fonction fold sur les listes au cadre de types algébriques de données quelconques pouvant être décrit comme des algèbres initiales." 83 — @GDGLille
  44. CATAMORPHISME getUserById(1).cata( () => { // left return "this user

    does not exist" }, (user) => { // right return "Hello ${user.avatar}" } ) 84 — @GDGLille
  45. OPTION == MAYBE case class User(id: String, avatar: String) val

    users = List( User("panda", " ! "), User("fox", " " "), User("lion", " # ") ) 86 — @GDGLille
  46. find() ALWAYS RETURN AN Option users.find(user => user.id == "panda")

    // Some(User(panda, ! )) users.find(user => user.id == "tiger") // None 87 — @GDGLille
  47. DONC JE PEUX FAIRE CECI: users .find(user => user.id ==

    "panda") .map(user => user.avatar) .getOrElse(" ! ") // " or ! 88 — @GDGLille
  48. OU CELA: (CATAMORPHISME LIKE) users.find(user => user.id == "fox") match

    { case None => "this user does not exist" case Some(user) => s"Hello ${user.avatar}" } // ! 89 — @GDGLille
  49. ! IL Y A flatMap AUSSI case class User(id: Option[String],

    avatar: Option[String]) val users = List( User(Option("panda"), Option(" ! ")), User(Option("fox"), Option(" " ")), User(Option("lion"), Option(" # ")) ) println( users.find(user => user.id.get.equals("fox")) .flatMap(user => user.avatar) .getOrElse(" $ ") ) 90 — @GDGLille
  50. divide function divide(a, b) { try { if(typeof a !==

    "number") throw new Error(" a is not a number") if(typeof b !== "number") throw new Error(" b is not a number") if(b==0) throw new Error(" ! /0") return monet.Either.Right(a/b); } catch(err) { return monet.Either.Left(err.message) } } 93 — @GDGLille
  51. divide let result1 = divide(4,5) result1.isRight() ? console.log(result1.right()) : console.log(result1.left())

    // 0.8 let result2 = divide(4,0) result2.isRight() ? console.log(result2.right()) : console.log(result2.left()) // ! /0 divide(4,"quatre").cata(leftError => { console.error(leftError) }, rightSuccess => { console.info(rightSuccess) }) // ! b is not a number 94 — @GDGLille
  52. CÔTÉ SERVEUR EN JAVASCRIPT let users = [" ! ",

    " " ", " # ", " "] app.get('/users/:id', (req, res) => { let user = users[parseInt(req.params.id)] res.send(user) }); 96 — @GDGLille
  53. EITHER AVEC SCALA def getUser(uri: String, id: Int): Either[String, Any]

    = { try { fromURL(s"http://localhost:7070/${uri}/${id.toString}").mkString match { case "" => Left(" ! Empty") case content:String => Right(content) } } catch { case e: Exception => Left(s" ! ${e.toString}") } } 97 — @GDGLille
  54. EITHER (UTILISATION) getUser("users", 3) match { case Left(err) => println(err)

    case Right(content) => println(content) } // ! getUser("users",8) match { case Left(err) => println(err) case Right(content) => println(content) } // " Empty getUser("user",8) match { case Left(err) => println(err) case Right(content) => println(content) } // " java.io.FileNotFoundException: http://localhost:7070/user/8 98 — @GDGLille
  55. MORE "STYLISH" WITH Try def getUser(uri: String, id: Int): Try[String]

    = { Try( fromURL(s"http://localhost:7070/${uri}/${id.toString}").mkString ) } getUser("users", 3) match { ! case Failure(fail) => println(s" " ${fail.toString}") ! case Success(content) => content match { case "" => println(" " Empty") case _ => println(content) } } 99 — @GDGLille
  56. INTÉRESSANT AVEC VERT.X router.get("/divide/:a/:b").handler(context => { Try( context.request.getParam("a").get.toInt / context.request.getParam("b").get.toInt

    ) match { case Failure(e) => context.json(new JsonObject().put("message", e.getMessage)) case Success(result) => context.json(new JsonObject().put("result", result)) } }) 100 — @GDGLille
  57. BON, C'EST FINI ... ... POUR LA PARTIE "MONET.JS" MAIS

    IL Y A PLEIN D'AUTRES CHOSES JOUEZ AVEC 101 — @GDGLille
  58. COLLECTIONS (SCALA): map case class Toon(pseudo: String, avatar: String) val

    toons = List( Toon("Bob", " ! "), Toon("Jane", " " "), Toon("Sam", " # "), Toon("John", " $ ") ) val myList = toons.map(toon => toon.pseudo + toon.avatar) // List(Bob ! , Jane " , Sam # , John ) 103 — @GDGLille
  59. C'EST PLUS MOCHE EN JAVA List<String> myList = toons .stream()

    .map(toon -> toon.pseudo + toon.avatar) .collect(Collectors.toList()); 104 — @GDGLille
  60. COLLECTIONS (SCALA): flatMap case class Toon(pseudo: String, avatar: String, buddies:

    List[Toon]= List()) val toons = List( Toon("Bob", " ! ", List(Toon("Ted", " " "), Toon("Polo", " "))), Toon("Jane", " $ ", List(Toon("Zed", " "), Toon("Grou", " "))), Toon("Sam", " ' ", List(Toon("Kate", " "), Toon("Nike", " "))), Toon("John", " * ", List(Toon("Ray", " "), Toon("Zoe", " "))) ) val myList = toons.flatMap(toon => toon.buddies).map(toon => toon.avatar) //List( " , # , % , & , ( , ) , + , , ) 105 — @GDGLille
  61. UNION TYPES Ceylon, Golo, ... Un type avec plusieurs sous-types,

    et il est un de ces sous-types, mais toujours un seul à la fois 107 — @GDGLille
  62. UNION TYPES | TYPESCRIPT class Yum { value: String constructor(value)

    { this.value = value; } } class Yuck { value: String constructor(value) { this.value = value; } } class Dunno { value: String constructor(value) { this.value = value; } } 109 — @GDGLille
  63. UNION TYPES | TYPESCRIPT function eat(food: String): Yuck | Yum

    | Dunno { if(food==' ! ') return new Yum(food) if(food==' " ') return new Yuck(food) return new Dunno(food) } console.log(eat(' ! ') instanceof Yum) console.log(eat(' ! ') instanceof Yuck) console.log(eat(' # ') instanceof Dunno) 110 — @GDGLille
  64. EN GOLO ÇA DONNERAIT CECI: union SomeThing = { Yum

    = { value } # good Yuck = { value } # bad Dunno = { value } # I don't know } let eat = |food| { if food is " ! " { return SomeThing.Yum(food)} if food is " " " { return SomeThing.Yuck(food)} return SomeThing.Dunno(food) } let res = eat(" ! ") match { when res: isYum() then println(" # ") when res: isYuck() then println(" $ ") otherwise " % " } 111 — @GDGLille
  65. UNION-TYPE.JS let SomeThing = Type({ Yum:{value: String}, Yuck:{value: String} })

    let good = SomeThing.Yum(' ! ') let bad = SomeThing.Yuck(' " ') 114 — @GDGLille
  66. UNION-TYPE.JS good.case({ Yum: (v) => { console.log(" ", v) },

    Yuck: (v) => { console.log(" ", v) } }) bad.case({ Yum: (v) => { console.log(" ", v) }, Yuck: (v) => { console.log(" ", v) } }) 115 — @GDGLille
  67. LIBS & REPOS Monet.js: https://cwmyers.github.io/monet.js/ Ramda.js: http://ramdajs.com/0.21.0/index.html Underscore: http://underscorejs.org/ Lodash:

    https://lodash.com/ Folktale: http://folktalejs.org/ Fantasy Land: https://github.com/fantasyland 119 — @GDGLille
  68. LECTURES Mostly Adequate Guide BRIAN LONSDORF HTTPS://WWW.GITBOOK.COM/BOOK/DRBOOLEAN/MOSTLY-ADEQUATE-GUIDE/DETAILS Functional Programming in

    Java PIERRE-YVES SAUMONT HTTPS://WWW.MANNING.COM/BOOKS/FUNCTIONAL-PROGRAMMING-IN-JAVA Functional Programming in JavaScript LUIS ATENCIO HTTPS://WWW.MANNING.COM/BOOKS/FUNCTIONAL-PROGRAMMING-IN-JAVASCRIPT Functional Programming in Scala PAUL CHIUSANO AND RÚNAR BJARNASON 120 — @GDGLille
  69. CODES SOURCE & ARTICLES Le code source de Golo: HTTPS://GITHUB.COM/ECLIPSE/GOLO-LANG/BLOB/MASTER/SRC/MAIN/GOLO/

    ERRORS.GOLO HTTPS://GITHUB.COM/ECLIPSE/GOLO-LANG/BLOB/MASTER/SRC/MAIN/JAVA/ GOLOLANG/ERROR/RESULT.JAVA Les monades en PHP: HTTP://MCAMUZAT.GITHUB.IO/BLOG/2015/11/11/LES-MONADES-EN-PHP-CEST- POSSIBLE-DOT/ 121 — @GDGLille
  70. TALKS Learning Functional Programming with JavaScript - JSUnconf 2016 ANJANA

    VAKIL HTTPS://WWW.YOUTUBE.COM/WATCH?V=E-5OBM1G_FY Gérer les erreurs avec l'aide du système de types de Scala ! DAVID SFERRUZZA HTTPS://WWW.YOUTUBE.COM/WATCH?V=TWJQKRZ23VS TDD, comme dans Type-Directed Development CLÉMENT DELAFARGUE 122 — @GDGLille