Black Magic Javascript Tricks

& Things I wish Hack Reactor Had Taught Me Part II

 

Sr. Front End Engineer, Deverus

HackReactor Austin (nee MakerSquare) Cohort #21

brian.boyko@gmail.com

Brian Boyko

not necessarily "good" code.

interesting code.

some of this is crazy juju

Real "black magic" stuff.

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

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.

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.

Why all this talk about pointers, when JS doesn't, strictly speaking, use them?

Because Web Assembly does.

Web Assembly

  • Takes C++/C code, and compiles it to be run via browsers using *native* hardware capabilities.
  • In other words: All the cool speed optimizations for C/C++ and all the hardware support can be done in the browser.

These apps have pretty much been replaced by web services:

  • Email Clients (Eudora, Thunderbird)
  • Schedulers (Outlook)
  • Instant Messaging Clients (AIM, ICQ)

These apps haven't yet - but could be.

  • High End Graphics Programs (Adobe Photoshop, Apple Aperture)
  • High end audio (Logic Pro)
  • High end video (Adobe Premiere, Final Cut Pro)
  • Most games (Crysis, Call of Duty, Zelda)

You may end up using WebAssembly, or not. But understanding pointers, whether or not you use them, makes you a stronger dev. 

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. 
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"),
    guam: armNuke("Guam"),
}

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.

From functions

to functors

what is a functor?

basically, anything you can .map() over.

Hey, you know what has .map()? 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

mak mor functor!!!!!!!!!!!

class MappableDictionary {
  constructor(obj){
    this.value = obj;
  }
  map(f) {
    return new MappableDictionary(
        Object.keys(this.value)
          .reduce((pv, key) => 
            Object.assign(pv, {
              [key]: f(this.value[key])
            }), {}))
    
  }
}

let namegame = new MappableDictionary({
  Adam: "Adam",
  Jill: "Jill",
  Chuck: "Chuck",
})

const letsDoB = (st) => st + " " + 
  st + " Bo-B" + st.substring(1); 

const firstVerse = namegame.map(letsDoB);
console.log(firstVerse.value)
//{ 
//  Adam: "Adam Adam Bo-Bdam",
//  Jill: "Jill Jill Bo-Bill",
//  Chuck: "Chuck Chuck Bo-Bhuck", 
//}
const MappableObject = (obj) => ({
    value: obj,
    map: (f) => MappableObject(Object.keys(obj)
      .reduce((pv, key) => 
        Object.assign(pv, {
          [key]: f(obj[key])
        }), {}))
})

const sorter = (arr) => arr.sort();
  
const stuff = {
  fruit: ['cherries', 'apples', 'bananas'],
  numbers: [3, 7, 2],
  loneString: "I'm so lonely"
}
let MappableStuff = MappableObject(stuff); 
console.log(MappableStuff
    .map((x) => Array.isArray(x) 
        ? sorter(x) 
        : x)
    .value)

// {
//   fruit: ["apples", "bananas", "cherries"],
//   numbers: [2, 3, 7],
//   loneString: "I'm so lonely"
// {

How is this useful?

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.

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

That's it for the black magic.

Now for the more practical stuff I've learned since I last talked here.

Things I wish Hack Reactor Taught Me Part II

Last time I spoke, I was a Jr. Engineer.

Now I'm a senior engineer.

It still holds true that the main difference between a Jr. and Sr. engineer is "how much you need to be mentored" vs. "how much mentoring you can do" but there are other differences.

Essentially, engineering is all about cooperation, collaboration, and empathy for both your colleagues and your customers. If someone told you that engineering was a field where you could get away with not dealing with people or feelings, then I’m very sorry to tell you that you have been lied to.

 

Solitary work is something that only happens at the most junior levels, and even then it’s only possible because someone senior to you — most likely your manager — has been putting in long hours to build up the social structures in your group that let you focus on code.

 

-- Yonatan Zunger, former engineer at Google

Jr. Engineer

  • Is told what to do in order to achieve goals.

     
  • Finds a workable solution.

     
  • Works tactically, and eventually learns to recognize and avoid "over-engineering"

Sr. Engineer

  • Tells manager what team must do to achieve goals.
     
  • Finds many solutions, chooses the best one based on criteria.
     
  • Works strategically, knows when and where "over-engineering" is a waste -- or a timesaver in the long run. 

Jr. Engineer

  • Uses best practices to get the best out of their own development.
     
  • Focus learning on "depth" - better understanding the languages and frameworks relevant to the job. (Frameworks, ES8, tooling, CI, etc.)

Sr. Engineer

  • Uses best practices to get the best out of their team.  
     
  • Can start focus learning on "breadth" - studying concepts not necessarily immediately related to work in order to apply those concepts to work. (Functional Programming, Algorithm Optimization...)

The Mobile World.

Most users of web applications are doing so from a mobile device.

 

If you have limited money, why buy a $1000 computer and $70 broadband/mo connection when you can pay only $70/mo for a phone that you need anyway?

But this presents us with difficulties.

PLUS mobile phones are big in markets that don't use Latin alphabetic characters.

Our Goal:

Interactivity on a 3G connection in 5 seconds or less.

Consider reinventing the wheel.

There are some *great* libraries out there that contain a ton of functions and components that make your site awesome. But each library you import adds more bloat. 

Some Sizes (minified, not gzipped)

  • Ember: 435kb
  • Angular 2 + Rx: 766kb
  • React + React DOM + Redux: 139kb
  • Vue: 58.8kb

3G can be as slow as 250k/sec

  • Preact: 16kb (works *almost exactly* like React)
  • Mithril: 8kb
  • Moon: 6kb
  • Hyperapp: 1kb.

I would recommend using React or Angular for your thesis project, for employability, but consider these for other projects once you graduate

Frameworks

Some Sizes (minified, not gzipped)

  • Material UI: 500kb - 3MB (depending on imports)
  • React-Bootstrap: ~400kb
  • Lodash: ~150kb

3G can be as slow as 250k/sec

Libraries

Sometimes it pays to "reinvent the wheel" if you only need a part of the wheel. 

 

If you only need a few methods from Lodash/Underscore, consider writing them from scratch yourself instead of importing them from the library. 

Typed Javascript

Typescript & Flow

  • Makes code easier to read.
  • Eliminates the need for a lot of boilerplate "docblockr" type documentation. 
  • Makes refactoring more reliable.
  • Better IDE support
  • Allows you to eliminate a lot of
      "expect(foo).to.be.a.("type") unit tests.

But...

  • It's not standard javascript.
  • Complex data types (Objects, arrays, functions that also have methods) can be hard to write the correct type for.
  • Writing typed code is a big productivity hit, so you may end up spending more time than you need to write code. 

When you should consider typed Javascript

  • If your code is part of a larger application operated on by a large team or a large number of teams. 
  • If your code is crucial to the success of the company and will be very long lived and pass through a large number of hands. 
  • If you are using Angular (which pretty much needs Typescript.) 

When you probably should avoid typed Javascript

  • Your project is not expected to be developed over a long period of time.
  • Your project is relatively simple (a SPA with a single database)
  • You are under time pressure, and the cost of adding/including a type system is significant.

Typescript

  • Invented by Microsoft, used at Google
  • Is a superset of Javascript that transpiles to Javascript.
  • Practically a requirement for Angular 2+
  • Checks type during transpilation step, throwing errors and stopping transpilation if type does not match. 

Flow

Typed Javascript: Typescript & Flow

  • Invented by Facebook.
  • Stripping out all flow notation returns valid Javascript that does not need transpilation.
  • Can check types at any time, including during unit testing. 
  • Flow type notation is stripped out of the code during the build process.
  • Incorrect typing will throw a warning but won't prevent transpiling.

this is the part in the lecture

where I take your questions

a.m.a.

email: brian.boyko@gmail.com

github: github.com/brianboyko

linkedin: linkedin.com/in/brianboyko

Black Magic Javascript

By brianboyko

Black Magic Javascript

  • 786