and why you really should be using TypeScript
brian.boyko@gmail.com
Brian Boyko
This stuff can be powerful.
It can be clever.
But if you're working with developers not used to these ideas, stick with the classics.
0x080483e4
0x080483f0
0x080483f2
0x80484e03
If you're coming from university, you probably actually at least have worked with pointers before, but a lot of self-taught and code-school devs haven't really worked with them a whole lot.
Which is fine, but understanding pointers can help you better understand the programming languages (like JS) that don't use them.
Typically used in "lower level" languages like C and C++.
Pointers are memory address values.
Imagine all your computer memory is accessible from a single array. The pointer is the index value.
It's called a "pointer" because you usually use it to "point" to the memory address you want the computer to access.
dun
dun
dun
(yes, but you don't have to worry about it.)
// LANGUAGE: C
int getNextRandomValue(void){
return rand(); // C's version of Math.random();
}
void populate_array(
int *array,
size_t arraySize,
int (*somePointerToAFunction)(void) // what's going on here?
){
for (size_t i=0; i < arraySize; i++)
array[i] = somePointerToAFunction();
}
int main(void){
int myarray[10];
populate_array(myarray, 10, &getNextRandomValue);
}
Unlike Javascript, C doesn't have
higher order functions.
You can't pass in a function as a parameter. Only ints, chars, and a few other primative types.
But... in C:
// LANGUAGE: JAVASCRIPT ES6
const getNextRandomValue = () => Math.random()
const populateArray = (arr, arrSize, callbackRandom) => {
for(let i = 0; i < arrSize, i++){
arr.push(callbackRandom());
}
}
const main = () => {
populateArray([], 10, getNextRandomValue);
}
The exact same thing.
The computer isn't smart enough to know what a function is. But Javascript abstracts that information for you when you need it.
Whenever you "pass by reference" instead of "pass by value," what you're really doing is passing the value of a pointer.
ain't.
that's why
That value cannot be reassigned.
// LANGUAGE: JAVASCRIPT ES6
var a = 1;
console.log(a); //=> 1
a = 2;
console.log(a); //=> 2
let b = 1;
console.log(b); //=> 1
b = 2;
console.log(b); //=> 2
const c = 1;
console.log(c); //=> 1
c = 2
//=> Uncaught TypeError: Assignment to constant variable.
let a = "foo" // a is assigned to "foo"
let b = { bar: "foo" } // b is assigned to the object?
const c = {bar: "foo"} // well, if that's true...
// then c should ALWAYS equal {bar: "foo"}
// LANGUAGE: NODE REPL
$ node
> const c = {bar: "foo"}
undefined
> c
{ bar: 'foo' }
> c = {bar: "unfoo"}
TypeError: Assignment to constant variable.
at repl:1:3
at ContextifyScript.Script.runInThisContext (vm.js:44:33)
at REPLServer.defaultEval (repl.js:239:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:433:10)
at emitOne (events.js:120:20)
at REPLServer.emit (events.js:210:7)
at REPLServer.Interface._onLine (readline.js:278:10)
at REPLServer.Interface._line (readline.js:625:8)
> c.bar = "unfoo"
'unfoo'
> c
{ bar: 'unfoo' }
>
You can't change a variable declared with const.
But if that variable is really just a pointer, pointing to a reference, you can modify the value at that reference.
Object.freeze(obj) is part of the JS language.
> c
{ bar: 'unfoo' }
> Object.freeze(c)
{ bar: 'unfoo' }
> c
{ bar: 'unfoo' }
> c.bar = "refoo"
'refoo'
> c
{ bar: 'unfoo' }
>
> const d = {baz: {bang: "biz"}}
undefined
> d
{ baz: { bang: 'biz' } }
> Object.freeze(d)
{ baz: { bang: 'biz' } }
> d.baz
{ bang: 'biz' }
> d.baz = "woo"
'woo'
> d.baz
{ bang: 'biz' }
> d.baz.bang = "innerwoo"
'innerwoo'
> d.baz.bang
'innerwoo'
> d
{ baz: { bang: 'innerwoo' } }
>
This is why immutable.js is a thing.
To fix this, you'd have to recursively freeze all objects inside of an object.
or use something *other* than a 'nilla JS object
here's one way to do it.
export const getFromServer = (endpoint, token, queryParameters) =>
new Promise((resolve, reject) => {
let url = makeURL(endpoint, token); // just creates the URL string.
request
.get(url)
.query(queryParams)
.then(res => resolve(res.body))
.catch((err, res) => reject({ error: true, msg: err, res }));
});
Take this function. Please.
You *could* remember that every time you invoke this function, you need to remember the right endpoints and the right token... or.....
export const getFromServer = (endpoint, token, queryParameters) =>
new Promise((resolve, reject) => {
let url = makeURL(endpoint, token); // just creates the URL string.
request
.get(url)
.query(queryParams)
.then(res => resolve(res.body))
.catch((err, res) => reject({ error: true, msg: err, res }));
});
const curriedGet = (token) =>
(endpoint) =>
(queryParameters) =>
getFromServer(endpoint, token, queryParameters);
export const makeAPI = (token) => ({
users: curriedGet(token)("/users"),
blogs: curriedGet(token)("/blogs"),
pictures: curriedGet(token)("/pictures")
})
// in the code.
import TOKEN from '../constants/token.js';
import { makeAPI } from '../api'
const api = makeAPI(TOKEN);
api.users({user: "Phil"})
.then((data) => console.log(data))
api.blogs({id: 32830})
.then((data) => console.log(data))
api.pictures()
.then((data) => console.log(data))
it helps with
"same kinda thing,
different configuration" problems.
const fbGenerator = (fizzer, buzzer) =>
(start, end) => {
let output = [];
for (let i = start; i < end; i++) {
if (i % fizzer === 0 && i % buzzer === 0) {
output.push("Fizzbuzz");
} else if (i % buzzer === 0) {
output.push("Buzz");
} else if (i % fizzer === 0) {
output.push("Fizz");
} else {
output.push(i);
}
}
return output;
};
const classicFizzbuzz = fbGenerator(3, 5);
classicFizzbuzz(1, 20)
// => [1,2,"Fizz",4,"Buzz","Fizz",7,8,"Fizz",
// "Buzz",11,"Fizz",13,14,"Fizzbuzz",16,17,"Fizz",19]
const funkyFizzbuzz = fbGenerator(4, 7)
funkyFizzbuzz(1, 20)
// => [1,2,3,"Fizz",5,6,"Buzz","Fizz",9,10,11,
// "Fizz",13,"Buzz",15,"Fizz",17,18,19]
// and yes, you can skip a step.
fbGenerator(3, 5)(1, 10)
// => [ 1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz' ]
so, why curry?
so, when curry?
One use case:
for junior devs: It allows them to be productive right away while allowing them to peek under the hood.
for senior devs: It allows them access to extend the features of the library and to improve the algorithms within
It should be noted that no ethically-trained software engineer would ever consent to write a nukeLasVegas() function.
-- quote modified from Nathaniel Borenstein
Basic professional ethics would instead require the creation of a nukeCity() function, to which Las Vegas can be provided as a parameter.
let armNuke = (target) => () => nuke(target);
let redButton = {
lasVegas: armNuke("Las Vegas"),
disneyWorld: armNuke("Disney World"),
ryanAir: (luggage) => {
if(luggage.kilos <= 8){
return armNuke("Ryan Air")
} else {
throw new Error("Nuclear Warhead cannot fit into overhead bin")
return gateCheck(armNuke('Ryan Air'));
}
}
}
simple. delicious.
const add = (a, b) => (a + b);
// ES6+
const curry = (f, ...curriedArgs) =>
(...newArgs) =>
f.apply(null, curriedArgs.concat(newArgs));
const addSeven = curry(add, 7)
// => (b) => 7 + b;
addSeven(3); // => 10;
oh, and one last thing:
some programming languages, including some "hot" ones like Elm, and Haskell, use currying to handle *any* function with more than one parameters.
-- ELM language
addfun: Int -> Int -> Int -> Int
addfun x y z = x + y + z
partiallyapplied1 = addfun 2
-- partiallyapplied1 = y z = 2 + y + z
google "function arity"
if for no other reason
than to sound smart.
A Dev's Best Testing Friend.
import database from 'database' //
import {config} from 'config'
let connection = database(config);
// can't test this function without actually connecting to the database.
export function addRecord (record) => {
connection.addOne(record)
}
// testable, database and config need to be defined in every single call.
export function addRecord (database, config, record) => {
let connection = database(config);
connection.addOne(record)
}
// AHA! Testable!
export const Connector = (database, config) => {
let connection = database(config);
return {
addRecord: function(record){
connection.addOne(record);
}
}
}
cannot test.
Very testable!
let testConnector =
Connector(fakeDB, fakeConfig);
testConnector
.addRecord(fakeRecord);
addRecord(fakeDB,
fakeConfig, fakeRecord);
// for every test
// and every use.
Hey, you know what has .map() in JS? Arrays.
console.log([1, 2, 3].map((x) => x * x)) //=> [1, 4, 9]
but what else can we map?
// object oriented
const curry = (f, ...curriedArgs) =>
(...newArgs) =>
f.apply(null, curriedArgs.concat(newArgs));
class Wrapper {
constructor(value){
this.value = value;
}
map(f){
return new Wrapper(f(this.value))
}
}
const something = new Wrapper(39)
console.log(something);
// => { value: 39 }
const add = (a, b) => a + b;
const addThree = curry(add, 3);
const somethingPlusThree =
something.map(addThree)
console.log(somethingPlusThree)
// => { value: 42 }
// little more functional,
// but functionally similar.
const Identity = (value) => ({
value: value,
map: (f) => Identity(f(value))
})
const square = x => x * x;
const idTwo = Identity(2);
console.log(idTwo)
// => Object {value: 2, map: function}
console.log(idTwo.value)
// => 2
console.log(idTwo
.map(square).value)
// => 4
console.log(idTwo
.map(square)
.map(square)
.map(square)
.map(square)
.value)
// => 65536
let num = 8
const addTwo = (x) => x + 2;
const double = (x) => x * 2;
const square = (x) => x * x;
const sqRoot = (x) => Math.pow(x, 0.5);
const subThree = (x) => x - 3;
let answer1 = addTwo(double(square(subThree(sqRoot(addTwo(double(square(num))))))));
//=> 143.17894898810343
This works. And is probably how you'd do it in most situations.
But isn't it kind of awkward?
It's kinda like... you're inside out.
What is 8 squared, times two, plus two, to the 0.5th power(i.e., square root), minus three, times two?
The code looks an awful lot like our english language problem. There's no real nesting of functions, so it might be easier to reason about.
What is 8 squared, times two, plus two, to the 0.5th power(i.e., square root), minus three, times two?
let answer2 = Identity(num)
.map(square)
.map(double)
.map(addTwo)
.map(sqRoot)
.map(subThree)
.map(square)
.map(double)
.value; //=> 143.17894898810343
const Identity = (value) => ({
value: value,
map: (f) => Identity(f(value))
})
let num = 8
const addTwo = (x) => x + 2;
const double = (x) => x * 2;
const square = (x) => x * x;
const sqRoot = (x) => Math.pow(x, 0.5);
const subThree = (x) => x - 3;
The tradeoff:
Novice developers probably won't get this right away. Even experienced developers unfamiliar with the pattern might not.
You're probably not going to create Identities for everything, as I mentioned, they're hard to get your head around, so they're not always easy for your team to parse.
let answer2 = Identity(num)
.map(square)
.map(double)
.map(addTwo)
.map(sqRoot)
.map(subThree)
.map(square)
.map(double)
.value; //=> 143.17894898810343
Now, Identity is a very *specific* use case of a monad. You can expand it a little bit to get this. And there's other monads too...
const Just = (value) => ({
inspect: (comment) => {
console.log(`Value ${value}`, comment)
return Just(value)
},
chain: (f) => f(value),
map: (f) => Just(f(value)),
applyMonad: (m) => m.map(value),
})
// how can we get the value?
// we could add a "value" method...
// or...
const identity = x => x;
Just("foo").chain(identity) // "foo"
const Identity = (value) => ({
value: value,
map: (f) => Identity(f(value))
})
Remember: A monad is just a monoid in the category of endofunctors
Don't worry, I don't know what that means either. A Monad's just a data structure, like linked lists or queues.
Check out "Functional-light JS"
by Kyle Simpson for more:
https://github.com/getify/Functional-Light-JS
const Just = (value) => ({
inspect: (comment) => {
console.log(`Value ${value}`, comment)
return Just(value)
},
chain: (f) => f(value),
map: (f) => Just(f(value)),
applyMonad: (m) => m.map(value),
})
const addTwo = (x) => x + 2;
const double = (x) => x * 2;
const square = (x) => x * x;
const sqRoot = (x) => Math.pow(x, 0.5);
const subThree = (x) => x - 3;
const identity = (x) => x;
Just(8)
.inspect("Initial") // => Value 8 Initial
.map(square)
.inspect("After square()") // > Value 64 After square()
.map(double)
.inspect("After double()") // > Value 128 After double()
.map(addTwo)
.inspect("After addTwo()") // > Value 130 After double()
.map(sqRoot)
.inspect("After sqRoot()") // > Value 11.40175425099138 After sqRoot()
.map(subThree)
.inspect("After subThree()") // > Value 8.40175425099138 After subThree()
.map(square)
.inspect("After square()") // > Value 70.58947449405171 After square()
.map(double)
.inspect("After double()") // > Value 141.17894898810343 After double()
.chain(identity); // => 143.17894898810343