Writing functional code with an OOP background.
My career has been heavy in JavaScript. I won't always have the answers to how things tie back to Ruby but I'm always happy help.
Ask questions as we go along. If something is confusing let me know because questions help me calibrate this talk to be for all skill levels.
Limiting the number of side effects that a block of code performs.
Breaking your code down into smaller chunks and putting it all together like Lego blocks.
A focus on the behavior of your code rather than the data that flows through it.
function
input
output
plusOne
1
2
const greet = (name) => {
console.log(`Hello, I'm ${name}!`)
}
const greet = (name) => {
console.log(`Hello, I'm ${name}!`)
}
greet('Bob');
// Hello, I'm Bobfunction foo () {
console.log('Hello!');
}
const foo = function () {
console.log('Hello!');
}
const foo = () => {
console.log('Hello!');
}There are many ways to write a function but for clarity I'm going to write all functions as arrow functions.
Functions can be passed around through variables or returned from other functions just like any other value.
var foo = () => {};
var foo = () => {
return () => {};
};Anything inside a function cannot be accessed or "seen" from outside the function. However, every function has access to all outer scopes.
Scope is the only thing that makes things "private"
const greet = () => {
var greeting = 'hello';
console.log(greeting);
};
greet();"hello"var greeting = 'hello';
const greet = () => {
console.log(greeting);
};
greet();"hello"const greet = () => {
var greeting = 'hello';
};
console.log(greeting);undefinedCompany X comes to you and tells you that they need you to program a simple robot. This robot needs to do four things.
Give the robot a name. "Bob"
Say farewell to a user. "Goodbye"
Keep track of the number of times it has greeted a user.
"I've greeted 2 times"
Greet the user with the robot's name. "Hello, I'm Bob"
Robot
greet
farewell
getGreetCount
class Robot {
}
class Robot {
constructor(robotName) {
this.robotName = robotName;
}
}
class Robot {
constructor(robotName) {
this.robotName = robotName;
}
greet(personName) {
console.log(
`Hello ${personName}, I'm ${this.robotName}`
);
}
}
class Robot {
constructor(robotName) {
this.robotName = robotName;
}
greet(personName) {
console.log(
`Hello ${personName}, I'm ${this.robotName}`
);
}
farewell() {
console.log('Goodbye!');
}
}
class Robot {
constructor(robotName) {
this.robotName = robotName;
this.greetCount = 0;
}
greet(personName) {
console.log(
`Hello ${personName}, I'm ${this.robotName}`
);
this.greetCount = this.greetCount + 1;
}
farewell() {
console.log('Goodbye!');
}
getGreetCount() {
console.log(
`I've greeted ${this.greetCount} times.`
);
}
}
const myRobot = new Robot('Bob');
myRobot.greet('Alec');
// Hello Alec, I'm Bob.
myRobot.farewell();
// Goodbye!
myRobot.getGreetCount()
// I've greeted 1 times.Company X has a brilliant idea. They want to build a robot dog. It has all the same characteristics as a robot except with the ability to bark.
How do we normally do this with classes?
class RoboDog extends Robot {
}class RoboDog extends Robot {
constructor(robotName) {
super(robotName);
}
}
class RoboDog extends Robot {
constructor(robotName) {
super(robotName);
}
bark () {
console.log('woof');
}
}
const myRoboDog = new RoboDog('Rover');
myRoboDog.greet('Alec');
// Hello Alec, I'm Rover.
myRoboDog.farewell();
// Goodbye!
myRoboDog.getGreetCount();
// I've greeted 1 times.
myRoboDog.bark();
// woofYour company releases the proprietary Robot and RoboDog API to the world. Everyone is free to extend your Robot class to make all kinds of new robots!
A few months later your company decides to rename the variable within the Robot function from "greetCount" to just "count".
class Robot {
constructor(robotName) {
this.robotName = robotName;
this.greetCount = 0;
}
...class Robot {
constructor(robotName) {
this.robotName = robotName;
this.count = 0;
}
...You publish the changes to NPM and the next day you get 100 new issues saying that you broke code relying on that count variable.
What you thought was a private implementation detail is breaking everything, why?
const myRobot = new Robot('Bob');class Robot {
constructor(robotName) {
this.robotName = robotName;
this.greetCount = 0;
}
greet(personName) {
console.log(
`Hello ${personName}, I'm ${this.robotName}`
);
this.greetCount = this.greetCount + 1;
}
farewell() {
console.log('Goodbye!');
}
getGreetCount() {
console.log(
`I've greeted ${this.greetCount} times.`
);
}
}
Your boss says that you need to make the implementation details of robotName and greetCount private.
Instead of writing a class that you can extend with inheritance let's try to write a series of functions that you can compose together to create an almost identical API.
We're going to start by building the MVP then build on from there.
Again, if you have any questions please feel free to ask.
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob.Whenever I think of writing models, clients, or groupings of related business logic, I prefix the instantiation function with "create".
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob.const createRobot = (robotName) => {
return () => {
}
};
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob!const createRobot = (robotName) => {
};
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob!const createRobot = (robotName) => {
return () => {
console.log(`Hello, I'm ${robotName}!`);
}
};
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob!const createRobot = (robotName) => {
return () => {
console.log(`Hello, I'm ${robotName}!`);
}
};
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob!Functions that operate on other functions, either by taking them as arguments or by returning them
const createTrackingClient = (hostUrl) => {
return (eventData) => {
return axios.post(hostUrl, eventData);
}
}$('button').on('click', () => {});function Button () {
return <button>Hello</button>;
}
export default withState('counter', 'setCounter', 0)(Button);The previous class implementation allowed the robot to be personalized and greet the user by name.
const createRobot = (robotName) => {
return () => {
console.log(`Hello, I'm ${robotName}!`);
}
};
const greet = createRobot('Bob');
greet();
// Hello, I'm Bob!const createRobot = (robotName) => {
return () => {
console.log(
`Hello, I'm ${robotName}!`
);
}
};
const greet = createRobot('Bob', 'Alec');
greet();
// Hello, I'm Bob!const createRobot = (robotName, personName) => {
return () => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
}
};
const greet = createRobot('Bob', 'Alec');
greet();
// Hello Alec, I'm Bob!Pass both names on creation
const createRobot = (robotName) => {
return () => {
console.log(
`Hello, I'm ${robotName}!`
);
}
};
const greet = createRobot('Bob');
greet('Alec');
// Hello Alec, I'm Bob!const createRobot = (robotName) => {
return (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
}
};
const greet = createRobot('Bob');
greet('Alec');
// Hello Alec, I'm Bob!Pass robot name on creation, pass person name on greet
const createRobot = (robotName) => {
return (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
}
};Taking a function that would take in multiple arguments and instead spreading them into multiple functions that only take one argument.
const createAdder = (numberOne) => {
return (numberTwo) => {
numberOne + numberTwo;
}
};
const addOne = createAdder(1);
addOne(2);const add = (numberOne, numberTwo) => {
return numberOne + numberTwo;
};
add(1,2);Right now our createRobot function only returns the greet function, we need to add more functionality related to a robot. Let's add the farewell method.
How do we group similar functionality using our current pattern?
const createRobot = (robotName) => {
return (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
}
};
const greet = createRobot('Bob');
greet('Alec');
// Hello Alec, I'm Bob!;const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
}
}
};
const greet = createRobot('Bob');
greet('Alec');
// Hello Alec, I'm Bob!;const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
farewell: () => {
console.log('Goodbye!')
}
}
};
const myRobot = createRobot('Bob');
myRobot.greet('Alec');
// Hello Alec, I'm Bob!;
myRobot.farewell();
// Goodbye!const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
farewell: () => {
console.log('Goodbye!')
}
}
};
const greet = createRobot('Bob');
greet('Alec');
// Hello Alec, I'm Bob!;const myRobot = createRobot('Bob');
myRobot.greet('Alec');
// Hello Alec, I'm Bob!;const myRobot = new Robot('Bob');
myRobot.greet('Alec');
// Hello Alec, I'm Bob.const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
getGreetCount: () => {
},
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};
const myRobot = createRobot('Bob');
myRobot.greet('Alec');
// Hello Alec, I'm Bob.
myRobot.greet('Alec');
// Hello Alec, I'm Bob.
myRobot.getGreetCount();
// I've greeted 2 times.const myRobotOne = createRobot('Bob');
const myRobotTwo = createRobot('Jennifer');
myRobotOne.greet('Alec');
myRobotOne.getGreetCount();
// I've greeted 1 times.
myRobotTwo.greet('Alec');
myRobotTwo.greet('Alec');
myRobotTwo.getGreetCount();
// I've greeted 2 times.const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
...
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
...
}
};
const myRobotOne = createRobot('Bob')
const myRobotTwo = createRobot('Jennifer');
myRobotOne.greet('Alec');
myRobotOne.greet('Alec');
myRobotTwo.greet('Alec')
0
0
1
2
1
const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
...
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
...
}
};
const myRobotOne = createRobot('Bob')
const myRobotTwo = createRobot('Jennifer');
myRobotOne.greet('Alec');
myRobotOne.greet('Alec');
myRobotTwo.greet('Alec')
A closure is a function that holds onto, or "closes over", variables within it's scope even after code execution has moved out of that block as long as a reference remains to those variables.
We finished our Robot but how would we extend it to make the RoboDog?
const createRobot = (robotName) => {
let greetCount = 0;
return {
...
farewell: () => {
console.log('Goodbye!')
}
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
...
farewell: () => {
console.log('Goodbye!')
}
}
};
const createRoboDog = (robotName) => {
return {
...createRobot(robotName),
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
...
farewell: () => {
console.log('Goodbye!')
}
}
};
const createRoboDog = (robotName) => {
return {
...createRobot(robotName),
bark: () => {
console.log('woof');
}
}
};const createRobot = (robotName) => {
let greetCount = 0;
return {
...
farewell: () => {
console.log('Goodbye!')
}
}
};
const createRoboDog = (robotName) => {
return {
...createRobot(robotName),
bark: () => {
console.log('woof');
}
}
};
const myRoboDog = createRoboDog('Rover');
myRoboDog.bark();
// woof
myRoboDog.farewell();
// Goodbye!Functional programming is trying to break down your code into small pieces based on what it does, not what it's modeling.
const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const fareweller = () => {
return {
farewell: () => {
console.log('Goodbye!');
}
}
};
const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
}
}
};const fareweller = () => {
return {
farewell: () => {
console.log('Goodbye!');
}
}
};
const greeter = (nameToGreet) => {
};
const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
}
}
};const fareweller = () => {
return {
farewell: () => {
console.log('Goodbye!');
}
}
};
const greeter = (nameToGreet) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${nameToGreet}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
}
}
};
const createRobot = (robotName) => {
};const fareweller = () => {
};
const createRobot = (robotName) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${robotName}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
},
farewell: () => {
console.log('Goodbye!')
}
}
};const fareweller = () => {
return {
farewell: () => {
console.log('Goodbye!');
}
}
};
const greeter = (nameToGreet) => {
let greetCount = 0;
return {
greet: (personName) => {
console.log(
`Hello ${personName}, I'm ${nameToGreet}!`
);
greetCount = greetCount + 1;
},
getGreetCount: () => {
console.log(`I've greeted ${greetCount} times.`);
}
}
};
const createRobot = (robotName) => {
return {
...fareweller(),
...greeter(robotName)
}
};const woofer = () => {
return {
bark: () => {
console.log('woof');
}
}
}
const createRoboDog = (dogName) => {
return {
...greeter(dogName),
...fareweller(),
...woofer()
}
};class Robot {
constructor(robotName) {
this.robotName = robotName;
this.greetCount = 0;
}
greet(personName) {
console.log(
`Hello ${personName}, I'm ${this.robotName}`
);
this.greetCount = this.greetCount + 1;
}
farewell() {
console.log('Goodbye!');
}
getGreetCount() {
console.log(
`I've greeted ${this.greetCount} times.`
);
}
}
console.log(typeof Robot)"function"console.log(new Robot('Bob'));"object"When it's in your best interest to abstract implementation details away from your consumers.
When you're coding for something that is guided by behavior rather than directly tied to a data model.
Classes when working on data models on the backend. Many JavaScript libraries that interact with a database rely heavily classes.
When you're working with libraries or on the front end where reusability of behavior is important.
const createTracker = (
eventDictionary: IEventDictionary,
{
apiUrl,
trackingId,
transformRequest = graphqlTransformer,
ssr = false,
} = {}
) => {
validateEventDictionary(eventDictionary);
const usedApiUrl = apiUrl || getTrackingEndpoint();
let usedTrackingId = trackingId || uuid();
return {
setTrackingId: (newTrackingId: string): void => {
usedTrackingId = newTrackingId;
},
track: (
eventName: string,
properties?: ITrackedProperty
): Promise<boolean> => {
...
},
};
};
export default createTracker;Hopefully you can use some of these patterns to create abstractions in your own code. If you ever have questions, I'm always available.