Functional Programming

in Javascript

Content

  • About me
  • The Problem
  • What is FP?
  • Terminology
  • Use Cases
  • Standard Library
  • External Modules

About me

picture of me

Julian Tölle
Developer @ narando & TrackCode
Backend Development & Devops
Javascript for 16 months

The Problem


            getNestedValue({ foo: 1 }, 'foo') // 1
            getNestedValue({ foo: { bar: 2 } }, 'foo.bar') // 2
            getNestedValue({ hello: 'world' }, 'foo.bar') // undefined
          

The Problem


              function getNestedValue(object, propertyName) {
                if (!propertyName) throw new Error("Impossible to set null property");
                
                const parts = propertyName.split(".");
                const len = parts.length;
                let subObject = object;
                let i;
              
                for (i = 0; i < len; i++) {
                  if (!subObject || typeof subObject === "undefined") return undefined;
                  subObject = subObject[parts[i]];
                }

                return subObject;
              }
          

The Problem


            const parts = propertyName.split(".");
            const len = parts.length;
            let subObject = object;
            let i;
          
  • multiple temporary variables
  • occupying "mind-estate"

The Problem


              for (i = 0; i < len; i++) {
                if (!subObject || typeof subObject === "undefined") return undefined;
                subObject = subObject[parts[i]];
              }
          
  • changing state
  • hard to reason about

What is FP?

  • programming paradigm
  • evaluation of mathematical functions
  • functions are treated like values

Terminology

First-Class Functions

  • treating functions the same as other values
    ( Object, Number, String, Boolean)
  • function definition is allowed where a value is expected
const sum = (a, b) => a + b

                const object = {
                  foo: 1,
                  bar: true,
                  baz: sum
                }
              

Terminology

Higher-Order Functions

  • takes a function as a parameter || returns a function
  • callbacks

              const add3 = v => v + 3;
              const twice = (f) => (v) => f(f(v));

              // Call directly
              twice(add3)(3); // 9

              // Store as value
              const add6 = twice(add3);
              add6(3); // 9

              const add12 = twice(twice)(add3);
              add12(3); // 15
          

Terminology

Side Effect

  • mutating state outside of the scope
  • modifying a global variable
  • observable effect on the outside world
  • order matters

            // mutating state
            let counter = 0;
            const inc = () => counter += 1;
            
            // observable effect
            const log = data => console.log(data);

            // Order changes the result
            const fs = require('fs');
            fs.readFileSync('./foo.txt') // "Hello World!";
            fs.writeFileSync('./foo.txt', "Bar!");
            fs.readFileSync('./foo.txt') // "Bar!"
          

Terminology

Pure Function

  • no side effects
  • same parameters => same results
  • examples: sha1, Math.sin,

              const add3 = v => v + 3;

              add3(3); // 6
              add3(3); // 6
              
              Math.sin(Math.PI / 4) // ~= 0.707
              Math.sin(Math.PI / 4) // ~= 0.707
            

Use Cases

Let's try improving the code from earlier!

              function getNestedValue(object, propertyName) {
                if (!propertyName) throw new Error("Impossible to set null property");
                
                const parts = propertyName.split(".");
                const len = parts.length;
                let subObject = object;
                let i;
              
                for (i = 0; i < len; i++) {
                  if (!subObject || typeof subObject === "undefined") return undefined;
                  subObject = subObject[parts[i]];
                }

                return subObject;
              }
          

Use Cases

Replace the loop with Array.prototype.forEach

            function getNestedValue(object, propertyName) {
              if (!propertyName) throw new Error("Impossible to set null property");
            
              const parts = propertyName.split(".");
              // const len = parts.length;
              let subObject = object;
              // let i;
            
              parts.forEach(part => {
                if (!subObject || typeof subObject === "undefined") return undefined;
                subObject = subObject[part];
              });
            
              return subObject;
            }
          

Use Cases

Remove the side-effect by using Array.prototype.reduce

              function getNestedValue(object, propertyName) {
                if (!propertyName) throw new Error("Impossible to set null property");
              
                const parts = propertyName.split(".");
                // let subObject = object;                
                // parts.forEach(part => {
                //   if (!subObject || typeof subObject === "undefined") return undefined;
                //   subObject = subObject[part];
                // });
              
                const nestedValue = parts.reduce((object, part) => {
                  if (!object || typeof object === "undefined") return undefined;
                  return object[part];
                }, object);
                return nestedValue;
              }              
            

Use Cases

Extract the reducer function Array.prototype.reduce

              function getValue(object, propertyName) {
                if (!propertyName) throw new Error("Impossible to set null property");
              
                if (!object || typeof object === "undefined") return undefined;
                return object[propertyName];
              }
              
              function getNestedValue(object, propertyName) {
                const parts = propertyName.split(".");
              
                const nestedValue = parts.reduce(getValue, object);
              
                return nestedValue;
              }
              

Use Cases

Get rid of unnecessary code

                function getValue(object, propertyName) {
                  if (!propertyName) throw new Error("Impossible to set null property");
                
                  return typeof object === "undefined" ? undefined : object[propertyName];
                }
                
                function getNestedValue(object, propertyName) {
                  return propertyName.split(".").reduce(getValue, object);                
                }
                

Standard Library

Array.prototype.{function}

  • forEach
  • filter
  • map
  • reduce

Standard Library

Array.prototype.forEach


            const elements = ["a", "b", "c"];

            for(let i = 0; i < elements.length; i++) {
              console.log(elements[i]);
            }

            // functional equivalent
            elements.forEach(element => console.log(element));
          
Mostly used for side-effects

Standard Library

Array.prototype.filter


              const elements = [5, 10, 7];
    
              const predicateEven = num => num % 2;
              const predicateAboveEight = num => num > 8;

              elements.filter(predicateEven); // [5, 7]
              elements.filter(predicateAboveEight) // [10]
            

Standard Library

Array.prototype.map

Convert a value into another one

              // Simple Example
              const elements = [5, 10, 7];
              const timesTwo = num => num * 2;
              elements.map(timesTwo); // [10, 20, 14]
              
              // Advanced Example
              const users = [
                { name: "Donald", username: "realDonaldTrump" },
                { name: "Hillary", username: "HillaryClinton" },
                { name: "Barack", username: "BarackObama" }
              ];
              
              const usernames = users.map(user => user.username);
              // ["realDonaldTrump", "HillaryClinton", "BarackObama"]
              

Standard Library

Array.prototype.reduce

Combine multiple elements into an aggregate

                const elements = [5, 10, 7];

                const sum = (a, b) => a + b;
                elements.reduce(sum, 0); // 22
                
                const max = (a, b) => Math.max(a, b);
                elements.reduce(max, -Infinity); // 10


                

Standard Library

Chaining


            const users = [
              { name: "Donald", username: "realDonaldTrump", president: true },
              { name: "Hillary", username: "HillaryClinton", president: false },
              { name: "Barack", username: "BarackObama", president: false }
            ];
            
            const isPresident = user => user.president;
            const introduceUser = user => `Hi! My name is ${user.name} and my Twitter is @${user.username}`;
            const log = msg => console.log(msg);
            
            users
              .filter(isPresident)
              .map(introduceUser)
              .forEach(log);
            //  Hi! My name is Donald and my Twitter is @realDonaldTrump
                

External Modules

Immutable.js

  • build by Facebook
  • Persistent Data Structures
  • implements Map, Set, List
  • keep old object, return new object

External Modules

Immutable.js


            const numbers = [1, 2, 3];
            numbers.push(4); // numbers modified
            numbers; // [1, 2, 3, 4];

            const { List } = require('immutable');
            const immutableNumbers = new List([1, 2, 3]);
            immutableNumbers.push(4); // [1, 2, 3, 4]
            immutableNumbers; // [1, 2, 3]

            // Functions from earlier still work
            immutableNumbers.map(x => x * x); // [1, 4, 9]
          

References & Further Links