Functional with JavaScript Arrays
Attendees.map(a => a.awesome=true);
No JavaScript frameworks were created during the making of this presentation!
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
- takes array of numbers
- create iterator, `sum` variable & new array
-
iterate through original array
- push to new array the value of the current index original array item multiplied by itself
-
iterate through new array
- add value of current index of new array to the sum variable
- 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?
- maps from an array of integers to an array of its squares
- 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:
- take array of numbers
- filter out odd numbers
- map to halves of each value
- 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 )
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 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?
Start
Examples:
- App Started
- DOM Events (DOMContentLoaded, onClick, onSubmit, etc)
- HTTP Request Received
- HTTP Response Returned
- Database Result
- WebSocket Message
- Etc
state is data, event is data, it's all data
event + state
End
Examples:
- Render / Update the UI
- Trigger a DOM Event
- Create an HTTP Request
- Return an HTTP Response
- Save to DB
- Send WebSocket Message
- 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
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
Hey, The Sign Said "JavaScript Arrays!"
bringing it back...
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
Array: a Wonder Container
arrays in JavaScript have these verbs built in:
- filter
- slice
- concat
- map
- reduce
- forEach
these verbs are associated with a bigger paradigm
...we can tap into the great power that comes from composing these verbs together
Adoptable Concept
at a smaller scale
first, we must understand how to use the parts
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
or just search for "mdn array"
Slice
Slice
items.slice(0, 5)
Remove Items
- from array of n length containing x's
- to array of <= n length containing x's
Example
[1,2,3,'a']
.slice(0,3);
// [1,2,3]
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)
// [0, 2, 4, 6, 8, 10, 12, 14]
[
{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)
https://atendesigngroup.com/blog/array-map-filter-and-reduce-js
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), [])
[ 1, 2, 3, 3, 2, 1, 5, 2, 1 ]
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;
}, [])
);
splitEvery(3, [0,1,2,3,4,5,6,7,8,9])
[ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9 ] ]
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
const before = [
{ key: "year", value: "2016" },
{ key: "make", value: "Porsche" },
{ key: "model", value: "911 R" },
{ key: "color", value: "white" },
{ key: "msrp", value: "$184,900" }
]
{
year: "2016",
make: "Porsche",
model: "911 R",
color: "white"
}
From this Shape
To this Shape
without price
const after = before
.filter( ({key}) => !/msrp/i.test(key) )
.map( ({key, value}) => ({[key]: value}) )
.reduce( (acc, curr) => ({...acc, ...curr}), {} );
Answer
"What" all the things!
The End
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
By Bruce Campbell
Functional with JavaScript Arrays
- 1,118