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

Prog Fonctionnelle ๐Ÿ‘

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Prog Fonctionnelleย ๐Ÿ‘

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

Avatar for Philippe CHARRIERE

Philippe CHARRIERE

October 03, 2017

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 OU, "MAIS QU'EST CE QUE JE FAIS

    ICI?" 5 โ€” @GDGLille
  4. FP AND ME A monad is just a monoid in

    the category of endofunctors, what's the problem? โ€” Philip Wadler 7 โ€” @GDGLille
  5. 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
  6. MAIS POURQUOI DU JAVASCRIPT > facile ร  lire si si

    > simple ร  utiliser ES 2015 > fonctionnel depuis longtemps > encore plus depuis ECMAScript 5 13 โ€” @GDGLille
  7. KEY POINTS > pure function > function as input &

    output > no iteration > (im)mutability 15 โ€” @GDGLille
  8. PURE FUNCTIONS > Pour รฉviter les effets de bord >

    Toujours retourner le mรชme rรฉsultat pour les mรชmes paramรจtres 17 โ€” @GDGLille
  9. 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
  10. PURE FUNCTION function goodHey({name}) { // named parameter return `Hello

    ${name}`; } console.log(goodHey({name:"Bob"})); 19 โ€” @GDGLille
  11. 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
  12. 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
  13. 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
  14. CURRYING & PF > moins verbeuse > plus agrรฉable >

    rรฉ-utilisation 24 โ€” @GDGLille
  15. 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
  16. NO ITERATIONS PRร‰PARER UN KEBAB: > choisir des ingrรฉdients (sans

    frites, sans oignons, ...) > dรฉcouper > tout mettre ensemble 29 โ€” @GDGLille
  17. NO ITERATIONS let aliments = [ " ! " ,

    " " " , " # " , " $ " , " % " , " & " // ' pas d'emoji oignons ] 30 โ€” @GDGLille
  18. 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
  19. 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
  20. NO ITERATIONS: CHOISIR ET Dร‰COUPER NOUVEAU TABLEAU ingrรฉdients: [ 'morceaux

    de ! ', 'morceaux de " ', 'morceaux de # ', 'morceaux de $ ' ] 33 โ€” @GDGLille
  21. 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
  22. (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
  23. (IM)MUTABILITY > Ne jamais trandformer un tableau (liste, collection, ...)

    directement > utiliser map pour obtenir un nouveau tableau 37 โ€” @GDGLille
  24. (IM)MUTABILITY let bananesAlaPlaceDesOignons = (aliment) => aliment == " !

    " ? " " : aliment let aliments = [' # ', ' $ ', ' % ', ' ', ' ', ' '] aliments.map(bananesAlaPlaceDesOignons) // nouveau tableau // [ ' # ', ' $ ', ' % ', ' & ', ' ' ', ' ' ] 38 โ€” @GDGLille
  25. (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
  26. ! 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
  27. ! EN SCALA // === assembler ==== aka reduce aliments

    .filter(sansFrite) .map(decouper) .fold(" ! avec: ") { (tmp, ingredient) => tmp + ingredient + " "} 42 โ€” @GDGLille
  28. 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
  29. 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
  30. 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
  31. 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
  32. FUNCTOR > donc un Functor a une mรฉthode map() >

    et les tableaux en JavaScript aussi 55 โ€” @GDGLille
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. MAYBE AVEC MONET - UTILE - RECHERCHES getUserById(2) .orSome(" โš 

    no user") // { id: 2, name: 'sam', avatar: ' ' } getUserById(7) .orSome(" โš  no user") // โš  no user 80 โ€” @GDGLille
  43. MAYBE AVEC MONET - UTILE - .map() getUserById(2) .map((user) =>

    user.avatar ) .orSome(" ! ") // " getUserById(7) .map((user) => user.avatar ) .orSome(" ! ") // ! 81 โ€” @GDGLille
  44. 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
  45. CATAMORPHISME getUserById(1).cata( () => { // left return "this user

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

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

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

    "panda") .map(user => user.avatar) .getOrElse(" ! ") // " or ! 88 โ€” @GDGLille
  49. 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
  50. ! 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. BON, C'EST FINI ... ... POUR LA PARTIE "MONET.JS" MAIS

    IL Y A PLEIN D'AUTRES CHOSES JOUEZ AVEC 101 โ€” @GDGLille
  59. 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
  60. C'EST PLUS MOCHE EN JAVA List<String> myList = toons .stream()

    .map(toon -> toon.pseudo + toon.avatar) .collect(Collectors.toList()); 104 โ€” @GDGLille
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. UNION-TYPE.JS let SomeThing = Type({ Yum:{value: String}, Yuck:{value: String} })

    let good = SomeThing.Yum(' ! ') let bad = SomeThing.Yuck(' " ') 114 โ€” @GDGLille
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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