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

Programmation fonctionnelle 🐑 en JS

Programmation fonctionnelle 🐑 en JS

Philippe CHARRIERE

March 15, 2017
Tweet

More Decks by Philippe CHARRIERE

Other Decks in Programming

Transcript

  1. AGENDA ! la PF et moi quelques bases container, functor,

    monad(e) Monet.js: Identity, Maybe, Either, Validation ⚠ il va y avoir du Scala Union types 3 — @MontpellierJug
  2. FP AND ME ! A monad is just a monoid

    in the category of endofunctors, what's the problem? — Philip Wadler 9 — @MontpellierJug
  3. FP AND ME ! ⚠ DISCLAIMER AVANT DE COMMENCER Tout

    le monde ne sera pas forcément d'accord L'objectif c'est de commencer à comprendre 2, 3 choses 12 — @MontpellierJug
  4. C'EST PARTI ... ⚠ LES EXEMPLES SONT (PRESQUE TOUS) EN

    ES2015 + NODE 13 — @MontpellierJug
  5. MAIS POURQUOI DU JAVASCRIPT !❓ > facile à lire si

    si > simple à utiliser ES 2015 > fonctionnel depuis longtemps > encore plus depuis ECMAScript 5 15 — @MontpellierJug
  6. KEY POINTS > imperative vs functional > pure function >

    function as input & output > no iteration > (im)mutability 17 — @MontpellierJug
  7. IMPERATIVE STYLE ! "BASIC LIKE" var name1 = "Bob"; var

    message1 = "Hello"; var name2 = "Sam"; var message2 = "Hi"; console.log(`${message1} ${name1}`); console.log(`${message2} ${name2}`); 19 — @MontpellierJug
  8. FUNCTIONAL STYLE ! function hey({message, name}) { //! named parameter

    return `${message} ${name}`; } function hi({name}) { return hey({message: "Hi", name: name}) } function hello({name}) { return hey({message: "Hello", name: name}) } console.log(hello({name:"Bob"})); // ! reuse console.log(hi({name:"Sam"})); // ! reuse 20 — @MontpellierJug
  9. PURE FUNCTIONS Avoid side effect, using pure function use "pure

    function": - return always the same output for the same input not pure: - console.log() <> output, ... - using a variable declared outside the function ! It's not always possible " Think "purely" as possible 22 — @MontpellierJug
  10. 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 " 23 — @MontpellierJug
  11. PURE FUNCTION ✨" function goodHey({name}) { //! named parameter return

    `Hello ${name}`; } console.log(goodHey({name:"Bob"})); 24 — @MontpellierJug
  12. FUNCTIONS AS INPUT AND OUTPUT a function that returns a

    function that returns something etc... 26 — @MontpellierJug
  13. FUNCTIONS AS INPUT AND OUTPUT // 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(""#$"); 27 — @MontpellierJug
  14. FUNCTIONS AS INPUT AND OUTPUT // simplify code, lisibility, reuse

    console.log( youAreSoAmazing("Bob") ); // Bob, you are so ✨ console.log( youAreSoLovely("Sam") ); // Sam, you are so "#$ 28 — @MontpellierJug
  15. NO ITERATIONS More "functional style" don't iterate, (for, while) to

    transform data -> use map, reduce, filter 30 — @MontpellierJug
  16. NO ITERATIONS PRÉPARER UN KEBAB: > choisir des ingrédients (sans

    frites, sans oignons, ...) > découper > tout mettre ensemble 32 — @MontpellierJug
  17. NO ITERATIONS let aliments = [ "!" , """ ,

    "#" , "$" , "%" , "&" // ' pas d'emoji oignons ] 33 — @MontpellierJug
  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 !== """; } 34 — @MontpellierJug
  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) 35 — @MontpellierJug
  20. NO ITERATIONS: CHOISIR ET DÉCOUPER NOUVEAU TABLEAU ingrédients: [ 'morceaux

    de !', 'morceaux de "', 'morceaux de #', 'morceaux de $' ] 36 — @MontpellierJug
  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) 37 — @MontpellierJug
  22. NO ITERATIONS: TOUT METTRE ENSEMBLE "! avec: morceaux de "

    morceaux de # morceaux de $ morceaux de %" 38 — @MontpellierJug
  23. (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 ) ❓ 41 — @MontpellierJug
  24. (IM)MUTABILITY > Ne jamais trandformer un tableau (liste, collection, ...)

    directement > utiliser map pour obtenir un nouveau ✨ tableau 42 — @MontpellierJug
  25. (IM)MUTABILITY let bananesAlaPlaceDesOignons = (aliment) => aliment == "!" ?

    """ : aliment let aliments = ['#', '$', '%', '&', ''', '!'] aliments.map(bananesAlaPlaceDesOignons) // nouveau tableau // [ '#', '$', '%', '&', ''', '"' ] 43 — @MontpellierJug
  26. (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) 44 — @MontpellierJug
  27. CONTAINER❓ class Container { constructor(x) { const value = x;

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

    public T getValue() { return this.value; } public Container(T value) { this.value = value; } } 48 — @MontpellierJug
  29. FUNCTOR❓ class Functor extends Container { 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)) } } 57 — @MontpellierJug
  30. FUNCTOR❓ EN JAVA public class Functor<T> extends Container { public

    Functor(T value) { super(value); } public <R> Functor<R> map(Function<T,R> f) { return new Functor<R>(f.apply((T) this.getValue())); } } 59 — @MontpellierJug
  31. 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); } } 65 — @MontpellierJug
  32. MONAD❓ EN JAVA public class Monad<T> extends Container { 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()); } } 66 — @MontpellierJug
  33. IDENTITY AVEC MONET const monet = require('monet'); let panda =

    monet.Identity('!'); let addRabbit = (me) => me + """; let buddies = panda .map(addRabbit).map(addRabbit) .map(b => b + "#") console.log(buddies) // { val: '!""#' } console.log(buddies.get()) // !""# 72 — @MontpellierJug
  34. IDENTITY AVEC MONET UNE MONADE DANS UNE MONADE let addTiger

    = (me) => monet.Identity(me + '!') let otherBuddies = panda .map(addRabbit) .map(addRabbit) .map(addTiger) console.log(otherBuddies) // { val: { val: '"##!' } } console.log(otherBuddies.get()) // { val: '"##!' } 73 — @MontpellierJug
  35. IDENTITY AVEC MONET ON APLATIT ... let addTiger = (me)

    => monet.Identity(me + '!') let otherBuddies = panda .map(addRabbit) .map(addRabbit) .flatMap(addTiger) console.log(otherBuddies) // { val: '"##!' } console.log(otherBuddies.get()) // "##! 74 — @MontpellierJug
  36. 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 82 — @MontpellierJug
  37. 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: '"' } ) 83 — @MontpellierJug
  38. 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)) } 85 — @MontpellierJug
  39. MAYBE AVEC MONET - UTILE❓ - RECHERCHES getUserById(2) .orSome("⚠ no

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

    user.avatar ) .orSome("!") // " getUserById(7) .map((user) => user.avatar ) .orSome("!") // ! 87 — @MontpellierJug
  41. CATAMORPHISME❓ "In category theory, the concept of catamorphism denotes the

    unique homomorphism from an initial algebra into some other algebra." 89 — @MontpellierJug
  42. CATAMORPHISME❓ "In category theory, the concept of catamorphism denotes the

    unique homomorphism from an initial algebra into some other algebra." ! 90 — @MontpellierJug
  43. CATAMORPHISME❓ getUserById(1).cata( () => { // left return "this user

    does not exist" }, (user) => { // right return "Hello ${user.avatar}" } ) 91 — @MontpellierJug
  44. OPTION == MAYBE val users: Array[Map[String, String]] = Array( Map("id"

    -> "panda", "avatar" -> "!"), Map("id" -> "fox", "avatar" -> """), Map("id" -> "lion", "avatar" -> "#") ) 93 — @MontpellierJug
  45. find() ALWAYS RETURN AN Option users.find(user => user("id") == "panda")

    // Some(Map(id -> panda, avatar -> !)) users.find(user => user("id") == "tiger") // None 94 — @MontpellierJug
  46. DONC JE PEUX FAIRE CECI: users .find(user => user("id") ==

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

    { case None => "this user does not exist" case Some(user) => "Hello" + user("avatar") } // ! 96 — @MontpellierJug
  48. EITHER AVEC MONET UTILE❓ > DÉMO ⚠ RUN THE "

    SERVER > 03-either/either1.js > 03-either/either2.js 99 — @MontpellierJug
  49. SERVER SIDE let users = [ {id:1, name:"bob", avatar:"!"} ,

    {id:2, name:"sam", avatar:"""} , {id:3, name:"jane", avatar:"#"} , {id:4, name:"john", avatar:"$"} ] app.get('/users/:id', (req, res) => { let user = users.find(u => u.id == parseInt(req.params.id)) res.send(user !== undefined ? user : {}) }); 100 — @MontpellierJug
  50. getUserById let getUserById = (id) => { return fetch(`http://localhost:9090/users/${id}`, {

    method: 'GET', headers: {"Content-Type": "application/json"}, }) .then(response => response.json()) .then(jsonData => jsonData) .catch(err => err) } 101 — @MontpellierJug
  51. DEMO either1.js { FetchError: request to http://localhost:9090/users/1 failed, reason: connect

    ECONNREFUSED 127.0.0.1:9090 at ClientRequest.<anonymous> (/Users/k33g/Dropbox/k33g/fp/node_modules/node-fetch/index.js:133:11) at emitOne (events.js:96:13) at ClientRequest.emit (events.js:188:7) 103 — @MontpellierJug
  52. getUserById ! let getUserById = (id) => { return fetch(`http://localhost:9090/users/${id}`,

    { method: 'GET', headers: {"Content-Type": "application/json"}, }).then(response => response.json()) .then(user => { if(user.id === undefined) { ! return monet.Either.Left("" User unknown"); } ! return monet.Either.Right(user); }) ! .catch(err => monet.Either.Left(`" ${err.message}`)) } 104 — @MontpellierJug
  53. getUserById ! getUserById(1).then( result => { result.isRight() ? console.log(result.right()) //

    { id: 1, name: 'bob', avatar: '!' } : console.log(result.left()) // " User unknown }) 105 — @MontpellierJug
  54. getUserById ! map() cata() " ... getUserById(1).then(result => { result

    ! .map(user => user.avatar) ! .cata( left => console.log(left), // " right => console.log(right) // # ) }) 106 — @MontpellierJug
  55. EITHER def getUserById(id: Int): Either[String, Any] = { try {

    // OK, there is something more "stylish" val userOption = JSON.parseFull(fromURL("http://localhost:9090/users/"+id.toString).mkString) // JSON.parseFull returns an Option ! userOption match { " case None => Left("# Bad Json") case Some(user) => { user.asInstanceOf[Map[String, Any]].get("id") match { " case None => Left("# User unknown") " case Some(value) => Right(user) }}} } catch { " case e: Exception => Left("# " + e.toString) } } 108 — @MontpellierJug
  56. MORE "STYLISH" WITH Try ❤ def getUserById(id: Int): Either[String, Any]

    = { Try( JSON.parseFull(fromURL("http://localhost:9090/users/"+id.toString).mkString) ) match { ! case Failure(fail) => Left("" " + fail.toString) ! case Success(userOption) => { userOption match { ! case None => Left("" Bad Json") case Some(user) => { user.asInstanceOf[Map[String, Any]].get("id") match { ! case None => Left("" User unknown") ! case Some(value) => Right(user) }}}}} } 110 — @MontpellierJug
  57. VALIDATION AVEC MONET - ! PAS UNE MONAD(E)❗ Validation is

    not quite a monad as it doesn’t quite follow the monad rules, even though it has the monad methods. It that can hold either a success value or a failure value (i.e. an error message or some other failure object) and has methods for accumulating errors. -- Monet - la doc 113 — @MontpellierJug
  58. VALIDATION: SUCCESS AND FAIL let success = monet.Validation.success("You won❗ "");

    // { val: 'You won❗ "', isSuccessValue: true } let failure = monet.Validation.fail("You lost❗ #"); // { val: 'You lost❗ #', isSuccessValue: false } 114 — @MontpellierJug
  59. getUserById ! let getUserById = (id) => { return fetch(`http://localhost:9090/users/${id}`,

    { method: 'GET', headers: {"Content-Type": "application/json"}, }) .then(response => response.json()) .then(user => { if(user.id === undefined) { ! return monet.Validation.fail("" User unknown"); } ! return monet.Validation.success(user); }) ! .catch(err => monet.Validation.fail(`" ${err.message}`)) } 115 — @MontpellierJug
  60. getUserById ! map() cata() " ... getUserById(1).then(result => { result.map(user

    => user.avatar).cata( failure => console.log(failure), success => console.log(success) ) }) 116 — @MontpellierJug
  61. APPLICATIVE FUNCTOR PATTERN ! Implements the applicative functor pattern. ap

    will apply a function over the validation from within the supplied validation. If any of the validations are fails then the function will collect the errors. -- Monet - la doc 118 — @MontpellierJug
  62. VALIDER PLUSIEURS POINTS ET COLLECTER LES ERREURS let users =

    [ {id:1, name:"bob", avatar:"!"} , {id:2, name:"sam", avatar:"""} , {id:3, name:"jane", avatar:"#"} , {id:4, name:"john", avatar:"$"} , {id:5, name:"paul"} , {id:6, avatar:"%"} , {id:7} ] app.get('/users/:id', (req, res) => { let user = users.find(u => u.id == parseInt(req.params.id)) res.send(user !== undefined ? user : {}) }); 119 — @MontpellierJug
  63. "VALIDATORS" QUI RETOURNENT DES VALIDATIONS let isNotUndefined = user =>

    { return user.id !== undefined ? monet.Validation.success(user.id) : monet.Validation.fail(["! User unknown"]); // " have you seen the []? } 120 — @MontpellierJug
  64. "VALIDATORS" let hasName = user => { return user.name !==

    undefined ? monet.Validation.success(user.name) : monet.Validation.fail(["! No name ❗"]); } 121 — @MontpellierJug
  65. "VALIDATORS" let hasAvatar = user => { return user.avatar !==

    undefined ? monet.Validation.success(user.avatar) : monet.Validation.fail(["! No avatar ❗"]); } 122 — @MontpellierJug
  66. "LA FONCTION DU SUCCÈS" let onSuccessfulValidation = (id) => (name)

    => (avatar) => { return {id, name, avatar} } 123 — @MontpellierJug
  67. getUserById ! let getUserById = (id) => { return fetch(`http://localhost:9090/users/${id}`,

    { method: 'GET', headers: {"Content-Type": "application/json"}, }) .then(response => response.json()) .then(user => ! isNotUndefined(user) ! .ap(hasName(user) ! .ap(hasAvatar(user) ! .map(onSuccessfulValidation))) ) .catch(err => monet.Validation.fail(`" ${err.message}`)) } 124 — @MontpellierJug
  68. getUserById ! cata() " ... getUserById(1).then(result => { result.cata( errors

    => console.error("Errors:", errors), value => console.info("Success:", value) ) }) // Success: { id: '!', name: 'bob', avatar: 1 } 125 — @MontpellierJug
  69. getUserById ! cata() " ... getUserById(7).then(result => { result.cata( errors

    => console.error("Errors:", errors), value => console.info("Success:", value) ) }) // Errors: [ '! No name ❗', '# No avatar ❗' ] 126 — @MontpellierJug
  70. J'AI PAS EU LE COURAGE ! MAIS IL Y A

    DES LIBRAIRIES POUR ÇA > scalaz > cats 128 — @MontpellierJug
  71. SINON AVEC LES UNIONS TYPES ÇA SERAIT FACILE À CODER...

    > Dotty (http://dotty.epfl.ch/) > scalaz > Incantations ... 129 — @MontpellierJug
  72. INCANTATIONS ! type |∨|[T, U] = { type λ[X] =

    ¬¬[X] <:< (T ∨ U) } def size[T: (Int |∨| String)#λ](t: T) = t match { case i: Int => i case s: String => s.length } 130 — @MontpellierJug
  73. BON, C'EST FINI ... ! ... POUR LA PARTIE "MONET.JS"

    MAIS IL Y A PLEIN D'AUTRES CHOSES JOUEZ AVEC ! 131 — @MontpellierJug
  74. UNION TYPES ❓ Ceylon, Golo, ... Un type avec plusieurs

    sous-types, et il est un de ces sous-types, mais toujours un seul à la fois ! 133 — @MontpellierJug
  75. EN GOLO ÇA DONNERAIT CECI: union SomeThing = { Yum

    = { value } # it's good Yuck = { value } # it's bad } let banana = SomeThing.Yum("!") let hotPepper = SomeThing.Yuck(""") # you can do that: println(banana: isYum()) # true println(hotPepper: isYuck()) # true 134 — @MontpellierJug
  76. ... ON PIMP LES SOUS-TYPES: CATAMORPHISME augment SomeThing$Yum { function

    cata = |this, ifYuck, ifYum| -> ifYum(this: value()) } augment SomeThing$Yuck { function cata = |this, ifYuck, ifYum| -> ifYuck(this: value()) } 135 — @MontpellierJug
  77. CATAMORPHISME SomeThing.Yuck("!"): cata( |value| -> println("I " " + value),

    |value| -> println("I # " + value) ) SomeThing.Yum("$"): cata( |value| -> println("I " " + value), |value| -> println("I # " + value) ) 136 — @MontpellierJug
  78. ON PIMP ENCORE YUM augment SomeThing$Yum { function cata =

    |this, ifYuck, ifYum| -> ifYum(this: value()) function map = |this, fn| -> this.Yum(fn(this: value())) function ap = |this, something| -> something: cata( |errors| -> something , |value| -> something: map(this: value()) ) } 137 — @MontpellierJug
  79. ON PIMP ENCORE YUCK augment SomeThing$Yuck { function cata =

    |this, ifYuck, ifYum| -> ifYuck(this: value()) function map = |this, fn| -> this function ap = |this, something| -> something: cata( |errors| { errors: addAll(something: value(): clone()) return this.Yuck(errors) }, |value| -> this ) } 138 — @MontpellierJug
  80. VALIDATORS let checkMonth = |monthNumber| { if (monthNumber <=12 and

    monthNumber >0) { return SomeThing.Yum(monthNumber) } else { return SomeThing.Yuck(vector["bad month number"]) } } let checkDay = |dayNumber| { if (dayNumber <=7 and dayNumber >0 ) { return SomeThing.Yum(dayNumber) } else { return SomeThing.Yuck(vector["bad day number"]) } } 140 — @MontpellierJug
  81. VALIDATION ! " SomeThing.Yum( |day| -> |month| -> "Day:" +

    day + " Month:" + month ) : ap(checkDay(42)) : ap(checkMonth(42)) : cata( |err| -> println("!: " + err), |value| -> println("": " + value) ) # !: [bad month number, bad month number] # ": Day:2 Month:5 141 — @MontpellierJug
  82. UNION-TYPE.JS❓ let SomeThing = Type({ Yum:{value: String}, Yuck:{value: String} })

    let good = SomeThing.Yum('!') let bad = SomeThing.Yuck('"') 144 — @MontpellierJug
  83. 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) } }) 145 — @MontpellierJug
  84. C'EST POUR ÇA QUE J❤ LE JAVASCRIPT Tout est possible

    simplement 146 — @MontpellierJug
  85. 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 149 — @MontpellierJug
  86. 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 150 — @MontpellierJug
  87. 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/ 151 — @MontpellierJug
  88. 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 152 — @MontpellierJug