Functional with JavaScript Arrays

Part 1

Why?

Is Functional Programming Just Hipster Hype?

Is There Substance Behind Perceived Hype?

Is Software Development "Just Going Through One of Those Phases?"

does functional style programming actually lead to increased productivity, less bugs, reusable code, fun, etc?

one way to find out

From How to What

  1. takes array of numbers
  2. create iterator, `sum` variable & new array
  3. iterate through original array
    1. push to new array the value of the current index original array multiplied by itself
  4. iterate through new array
    1. add value of current index of new array to the sum variable
  5. return the sum variable

Uh, that's how

not what

function mysteryOperation(nums) {
    var i, sum = 0, squares = [];
 
    for (i = 0; i < nums.length; i++) {
        squares.push(nums[i]*nums[i]);
    }

    for (i = 0; i < squares.length; i++) {
        sum += squares[i];
    }

    return sum;
}

console.log(
    mysteryOperation( getArrayofIntegers() )
);

What does mysteryOperation do?

function mysteryOperation(nums) {
    var i, sum = 0, squares = [];
 
    for (i = 0; i < nums.length; i++) {
        squares.push(nums[i]*nums[i]);
    }

    for (i = 0; i < squares.length; i++) {
        sum += squares[i];
    }

    return sum;
}

console.log(
    mysteryOperation( getArrayofIntegers() )
);

What does mysteryOperation do?

  1. maps from an array of integers to an array of its squares
  2. reduces the array of squares to its sum
const mysteryOp = (nums) => nums
    .map( (x) => x * x )
    .reduce( (acc, c) => acc + c, 0);

in code -- what is how

Abstractly, it:

  1. take array of numbers
  2. filter out odd numbers
  3. map to halves of each value
  4. reduce to average
function mysteryOperation(nums) {
    var i, sum = 0, tally = 0;
    for (i = 0; i < nums.length; i++) {
        if(nums[i]%2===0) {
            sum += nums[i]/2
            tally++;
        }
    }
    return sum/tally;
}

What does this mysteryOperation do?

const isEven = (x) => x%2===0;
const half = (x) => x / 2;
const toAvg = (acc, c, i, arr) => (
    i < arr.length - 1
        ? acc + c
        : (acc + c) / arr.length
    ), 0)
const mysteryOperation = (nums) => nums
    .filter( isEven )
    .map( half )
    .reduce( toAvg )

that is telling app What to do vs How

Does This Idea Scale?

What Does an App Do?

In an App

  • There are things
  • Things have names
  • Things have functionality
  • Things have data
  • Things expose ways to interact with other things
  • (Things have a tendency to be very domain specific)

But that is the a How

What's the What?

you know, from an abstract level

What An App Does

you know, from an abstract level

An app is just a long running process in which an async series of events are transformed into effects

True or False?

Start, Transform, Affect, Repeat

  • start with an event + state
  • data + event flows through transformations (1, 2, 3, 4)
  • end with a state mutation & effect

start

1

2

3

4

end

an app listens for events, pipes them through transformations, ends them as effects

is that What an app does?

Buying It?

Start

Examples:

  1. App Started
  2. DOM Events (DOMContentLoaded, onClick, onSubmit, etc)
  3. HTTP Request Received
  4. HTTP Response Returned
  5. Database Result
  6. WebSocket Message
  7. Etc

state is data, event is data, it's all data

event + state

End

Examples:

  1. Render / Update the UI
  2. Trigger a DOM Event
  3. Create an HTTP Request
  4. Return an HTTP Response
  5. Save to DB
  6. Send WebSocket Message
  7. Etc

aka: the effect

A Start, an End, and a Bunch of Stuff In Between

Instead of things and their imperative operations, like:

  • myCat.setAttribute('name', 'felix');
  • reportCard.addGrade(myGrade);
  • mapper::getInstance().removeRecord(recordA);
  • MyRecord::fromAnotherObject( anotherObject );
  • etc.

How, not What

Stuff in Between

Declarative / Functional Style Programming Strives to Align

How with What

Transformations

so, between start & end, we can think of data streaming through a composition of transformative verbs. These verbs describe what those transformations do

  • filter
  • slice
  • map
  • reduce
  • concat
  • zip
  • fork
  • flatten
  • etc

start

1

2

3

4

end

Not Always This Simple

start

1

2

3

4

end

a

b

c

end

start

1

2

3

4

end

a

b

i

ii

5

start

1

2

3

4

end

start

state

1

2

3

4

end

event

state

Nonetheless

all the transformations in the middle can be represented with the verbs

  • DOM Events
  • App Started
  • HTTP Request
  • HTTP Response
  • Database Result
  • WebSocket Message
  • UI Update
  • DOM Event
  • HTTP Request
  • HTTP Response
  • DB Query
  • WebSocket Message

And These

to These

are effectively mapped

Bonus

follow some simple rules & best practices

(stateless, pure, no side-effects, etc)

and

These

become extremely reusable, easy to maintain, testable and have a simple mental model

& your app can scale horizontally

Verbs Included

many functional libraries include these verbs

(map, filter, reduce, etc)

  • rambda
  • underscore
  • lodash

 

Observables / Rx.js turns ALL THE THINGS (that change over time) into things that can be mapped, filtered, reduced, concat, etc

Hey, The Sign Said "JavaScript Arrays!"

bringing it back...

Adoptable Concept

at a smaller scale

typically, the data flowing into our app starts as an array

from endpoints, databases, etc

and we deal often with data in bulk in the form of Arrays

[...document.querySelectorAll('p')]

this array data eventually changes the DOM

or the data in this array may change the shape of the HTTP response

Array: a Wonder Container

arrays in JavaScript have these verbs built in:

  • filter
  • slice
  • concat
  • map
  • reduce
  • forEach

and now you know that these verbs are associated with a bigger paradigm

and we can tap into the great power that comes from composing these verbs together

first, we must understand how to use the parts

Slice

Slice

items.slice(0, 5)

Remove Items

  • from array of n length containing x's
  • to array of <= n length containing x's

 

Concat

Concat

items.concat([1, 2, 3])

Combine Items

  • from array of n length
  • to array of >n length 

 

Concat

  • accepts anything or an array of anythings
  • returns new array
    • contains previous items
    • appends new item or items
items.concat(1)
items.concat([1, 2, 3])

Examples

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .concat(11)


[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .concat([11, 12, 13, typeof window])

Map

Map

items.map(x => x * x)

Transform Each Item

  • from array of n length containing x's
  • to array of n length containing y's

 

Map

[].map((x) => x * x)

Accepts a Lambda Function

  • 1st argument of lamda is a placeholder of each item as language loops through the array (required)
  • Return value is added to the new array

 

lambda function accepts optional arguments 2 and 3, but they're less common

a higher-order function

[0, 1, 2, 3, 4, 5, 6, 7]
    .map((x) => 2**x)

// [1, 2, 4, 8, 16, 32, 64, 128]


[
    {w:10, h:20, d:10},
    {w:3, h:2, d:20},
    {w:4, h:1, d:400},
    {w:9000, h:3999, d:9191}
].map( ({w, h, d}) => w * h * d)


// [2000, 120, 1600, 330793281000]

Examples

Filter

Filter

items.filter((x) => x%2===0)

Remove Items

  • from array of n length
  • to array of <= n length

 

Filter

Accepts a Lambda Function

  • 1st argument of lamda is a placeholder of each item as language loops through the array (required)
  • Return value determines if item stays: truthy=stays, falsey=removed

 

lambda function accepts optional arguments 2 and 3, but they're less common

items.filter((x) => x%2===0)

a higher-order function

Examples

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    .filter((x) => x%2 ===0 );


["bacon cheeseburger", "chicken sandwich", "hamburger", "bacon salad"]
    .filter((x) => /bacon/i.test(x) );

Reduce

Reduce

items.reduce((acc, cur) => acc + cur, 0)

Transform From Array to Anything

  • from array of n length containing x's
  • to a y of anything

Reduce

[

]

array of blue circles

a red square

Reduce

lambda function accepts optional arguments 3 and 4, but they're less common

Accepts a Lambda Function

  • 1st argument of lambda is the accumulation of values that will eventually be the final results (required)
  • 2nd argument of the lambda is the current item as the array is iterated through
  • Return value becomes the accumulation in the next iteration and then the final result

Accepts an initial accumulation value

a higher-order function

items.reduce((acc, cur) => acc + cur, 0)

Reduce

parity of "types" through each iteration

1

3

2

5

6

8

0

1

4

6

11

17

25

[1, 3, 2, 5, 6, 8].reduce(
    (acc, cur) => acc + cur,
    0
);

What Else Besides Sum Can Reduce Do?

Shallow Flatten Arrays

[ [1, 2, 3], [3, 2, 1], [5, 2, 1] ]
    .reduce((acc, cur) => acc.concat(cur), [])

Split Arrays

const splitEvery = (chunkSize, arr) => (
    arr.reduce((acc, curr, i) => {
        const index = Math.floor(i/chunkSize);
            acc[index] = (acc[index]||[]).concat(curr);
            return acc;
    }, [])
);

Compose Functions

const compose = (...fns) => (
    (x) => fns.reduceRight(
        (acc, curr) => curr(acc),
        x
    )
);

Curry Functions

const curry = (fn) => (
    (...args) => (
        fn.length <=1 || args.length >= fn.length
            ? fn(...args)
            : args.reduce( (acc, curr) => (
                curry(acc.bind(null, curr)), fn)
            )
    )
);

Promise Thunk Waterfall

const waterfall = (...promiseThunks) => (
    () => promiseThunks.reduce(
        (acc, curr) => acc.then( () => curr() ),
        Promise.resolve()
    )
)

Demos

Exercise

[
    {
        key: "name", value: "jared"
    },
    {
        key: "age", value: "old",
    },
    {
        key: "food", value: "bacon"
    },
    {
        key: "kids", value: "gazillions"
    },
    {
	key: "garbageA", value: "valueA"
    },
    {
        key: "garbageB", value: "valueB"
    }
]
{
    name: "jared",
    age: "old",
    food: "bacon",
    kids: "gazillions"
}

From this Shape

To this Shape

without

"garbage"

const endShape = startShape
    .filter(({key}) => key.indexOf("garbage")===-1)
    .map( ({key, value}) => ({[key]: value}) )
    .reduce((acc, curr) => ({...acc, ...curr}), {})

Answer

Exercise

[
    {
        key: "name", value: "jared"
    },
    {
        key: "age", value: "old",
    },
    {
        key: "food", value: "bacon"
    },
    {
        key: "food", value: "pizza"
    },
    {
        key: "food", value: "cubby's"
    },
    {
        key: "food", value: "wings"
    },
    {
        key: "food", value: "shakes"
    }
];
{
    age: "old",
    kids: "gazillions",
    name: "jared",
    food: [
        "bacon",
        "pizza",
        "cubby's",
        "wings",
        "shakes"
    ]
}

From this Shape

To this Shape

startShape
    .map(({key, value}) => ({[key]: value}))
    .reduce((acc, curr) => {
        const key = Object.keys(curr)[0];
        return {
            ...acc,
            [key]: acc[key]
                ? [].concat( acc[key] ).concat( curr[key] )
                : curr[key]
        };
    }, {})

Answer

Exercise

Write a function which takes an array of any depth (nested) and returns a completely flattened array

Functional with JavaScript Arrays

Part 2

Covering

  • Function Composition
  • Function Currying
  • Programmatically Creating Lambda Function
  • Negating True/False Filter Functions
  • More Examples

To be continued...

Functional with JavaScript Arrays

By Jared Anderson

Functional with JavaScript Arrays

  • 1,586