Black Magic Javascript Tricks

and why you really should be using TypeScript

brian.boyko@gmail.com

Brian Boyko

adapted from a talk mainly for JS newbies, to get them thinking.

If you "already know this" please bear that in mind.

Why "Black Magic"?

and not just

not necessarily "good" code.

interesting code.

some of this is crazy juju

So, a warning.

Remember that we write code for two audiences.

  • The computer (who doesn't really care how it reads, so long as it compiles)
  • Other Programmers (who care very much, especially if you want to get stuff done.)

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.

Let's start off with a few simple pointers.

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. 

Pointers

Typically used in "lower level" languages like C and C++.  

 

Pointers are memory address values.  

imagine...

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. 

But, wait.  Javascript doesn't use pointers?

Or DOES IT?

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:

  • Every function has a memory address. 
  • Every memory address can be expressed as an integer
  • You can use: 
    • "&" to get the memory address of a function
    • "*" to get the function at a particular memory address.
// 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); 
}
 


What's going on here?

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.

const

 

ain't.

that's why

When you use the keyword "const"

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.

But what *is* the value of the variable, really? 

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. 

What if you really need to lock down an object? 

Object.freeze(obj) is part of the JS language.

  • it prevents reassignment
  • it prevents addition or deletion of keys
  • it prevents primitive values from being changed. 
  • but not values of
    nested objects!
> 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

next, let's talk functions

currying

also known as "partial application"

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.

 

  • Test vs. production database configs
  • similar api calls
  • file system operations
  • can be used pretty
    much anywhere you
    might use a class
    constructor. Only
    a little simpler.
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' ]

What, me curry?

so, why curry?

  • shorter, tighter code, that gets to the point in the fewest lines possible. 
  • less repetition.

so, when curry?

  • when writing out the functions for each case you need has become the kind of boring, repetitive task computers are good at. 

allows you to write code libraries and classes that work like "convention over configuration"

but still follows an "explicit is better than implicit" principle of coding.

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. 

Subtitle

-- 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')); 
      }
    }
}

cooking up
a good curry

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.

Currying & Functional Dependency Injection

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.

From functions

to functors

what is a functor?

basically, anything you can .map() over.

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?

Wrappers and Identities.

// 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

English Language Problem

What is 8 squared, times two, plus two, to the 0.5th power(i.e., square root), minus three, times two?

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.  

What about "real world" stuff?

let answer2 = Identity(num)
    .map(square)
    .map(double)
    .map(addTwo)
    .map(sqRoot)
    .map(subThree)
    .map(square)
    .map(double)
    .value;   //=> 143.17894898810343

But here's a very similar concept: Reducers

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

Black Magic Javascript II

By brianboyko

Black Magic Javascript II

  • 600