Skip to content

Instantly share code, notes, and snippets.

@abidibo
Last active November 26, 2020 14:13
Show Gist options
  • Save abidibo/bb14649e98cf855e069abce3254102ef to your computer and use it in GitHub Desktop.
Save abidibo/bb14649e98cf855e069abce3254102ef to your computer and use it in GitHub Desktop.
FP

FP in JS

Imperative VS Declarative

Imperative: tell the program how to do stuff, how the control flow goes and how the state changes. Declarative: tell the program what the logic is, describe the problem.

Declarative programs, specifically functional ones, raise the level of abstraction by using a minimally structured flow made up of independent blackbox operations that connect in a simple topology. These connected operations are nothing more than higher-order functions that move state from one operation to the next.

Example: quicksort algorithm

Imperative

Get a pivot (let's say the first element of the list), then loop through the list and find all the elements minor or equal to the pivot and keep them by side. Make the same with the elements greater than pivot. Then concatenate:

SUBLIST_LTE << PIVOT << SUBLIST_GT

And repeat the same algorithm on the two sublists until it's all sorted.

Declarative

A sorted list is a list that has all the values smaller than (or equal to) the pivot in front (and those values are sorted), then comes the pivot in the middle and then come all the values that are bigger than the pivot (they're also sorted)

Let's consider the pivot as the first element of the list.

let quicksort = ([pivot, ...rest]) => {
  if (!pivot) return []; // empty list: edge condition
  let smallerSorted = quicksort(rest.filter(item => item <= pivot ));
  let biggerSorted = quicksort(rest.filter(item => item > pivot));
  return [...smallerSorted, pivot, ...biggerSorted];
}

Example: calculate square of each element in a list

Imperative

function squareArray (list) {
  let newList = [];
  for (let i = 0, l = list.length; i < l; i++) {
    newList.push(Math.pow(list[i], 2));
  }
  return newList;
}

Declarative

let pow = x => y => Math.pow(y, x); // currying
let pow2 = pow(2);

function squareArray (list) {
  return list.map(pow2);
}

Immutability

class Person {
  constructor (name) {
    this._name = name;
  }
}

let person = Object.freeze(new Person('rufy'));
person._name = 'zoro'; // TypeError: Cannot assign to read only property '_name' of #<Person>

But Object.freeze is a shallow operation: nested objects are not freezed, you need to do it manually using recursion. Save yourself a lot of time and use a library providing immutability.

Functions

Fisrt class: actually, functions are objects.
Higher-Order: function can be passed as arguments and returned as values.

Currying

Example:

const logger = function(appender, layout, name, level, message) {
  const appenders = {
    'alert': new Log4js.JSAlertAppender(),
    'console': new Log4js.BrowserConsoleAppender()
  };
  const layouts = {
    'basic': new Log4js.BasicLayout(),
    'json': new Log4js.JSONLayout(),
    'xml' : new Log4js.XMLLayout()
  };
  const appender = appenders[appender];
  appender.setLayout(layouts[layout]);
  const logger = new Log4js.getLogger(name);
  logger.addAppender(appender);
  logger.log(level, message, null);
}
const log = R.curry(logger)('alert', 'json', 'FJS');
log('ERROR', 'Error condition detected!!');

const logError = R.curry(logger)('console', 'basic', 'FJS', 'ERROR');
logError('Error code 404 detected!!');

Composition

const str = `We can only see a short distance
  ahead but we can see plenty there
  that needs to be done`;
const explode = (str) => str.split(/\s+/);
const count = (arr) => arr.length;
const countWords = R.compose(count, explode);

countWords(str); //-> 19
const students = ['Rosser', 'Turing', 'Kleene', 'Church'];
const grades = [80, 100, 90, 99];

const smartestStudent = R.compose(
  R.head,
  R.pluck(0),
  R.reverse,
  R.sortBy(R.prop(1)),
  R.zip);
  
// declarative FTW!
  
const first = R.head;
const getName = R.pluck(0);
const reverse = R.reverse;
const sortByGrade = R.sortBy(R.prop(1));
const combine = R.zip;
R.compose(first, getName, reverse, sortByGrade, combine);

// but tied to this specific scenario => better to use pluck and friends

Use pipe to reverse orders, fills more natural:

.pipe(
  trim,
  normalize,
  findStudent,
  csv,
  append('#student-info'));

Functional combinators

Like compose ans pipe.

Identitiy

Returns the value it was provided

identity :: a -> a

Tap

Gets a void function and an argument, executes the given function on the given argument and returns it back! Allows void functions to be part of a pipeline (you couldn't have a void returning function in the pipeline).

tap :: (a -> *) -> a -> a

Example: logging.

const debug = R.tap(debugLog); // debugLog does not return anything: void, but debug returns the same object logged
const cleanInput = R.compose(normalize, debug, trim);
const isValidSsn = R.compose(debug, checkLengthSsn, debug, cleanInput);

Alt

Similar to an imperative if else. It takes two functions and returns a function which returns the result of the first one if not false/null/undefined, otherwise returns the result of the secondo function.

Possible implementation:

const alt = R.curry((func1, func2, val) => func1(val) || func2(val));

Sequence

It loops over the given functions applying them to the same input value. For example you can print a value to screen and log it at the same time.

Possible implementation:

const seq = function(/*funcs*/) {
  const funcs = Array.prototype.slice.call(arguments);
  return function (val) {
    funcs.forEach(function (fn) {
      fn(val);
    });
  };
};

Fork

It takes three function arguments: a join function and 2 terminal functions. Returns a function which runs the terminal functions against the provided value, and returns the result of the third (join) function which takes as arguments the results of the 2 prev functions (joins them).

Possible implementation:

const fork = function(join, func1, func2){
  return function(val) {
    return join(func1(val), func2(val));
  };
};

Example:

const computeAverageGrade = R.compose(getLetterGrade, fork(R.divide, R.sum, R.length));
computeAverageGrade([99, 80, 89]); //-> 'B'

Another one: are the mean and median equal?

const eqMedianAverage = fork(R.equals, R.median, R.mean);

Error handling

Imperative, try catch

try {
 var student = findStudent('444-44-4444');
}
catch (e) {
 console.log('ERROR' + e.message);
}

A function which uses try catch can't be composed (no referential transparency, impure, side effects)

Solution: functors.

Functors

Accessing a wrapped value can only be done by mapping an operation to its container. You can also think of map as a gate that allows you to plug in a lambda expression with specific behavior that transforms an encapsulated value. In the case of arrays, you used map to create a new array with the transformed values.

Container example:

class Wrapper {
  constructor(value) {
    this._value = value;
  }
  // map :: (A -> B) -> A -> B
  map(f) {
    return f(this.val);
  };

  toString() {
    return 'Wrapper (' + this.value + ')';
  }
}
// wrap :: A -> Wrapper(A)
const wrap = (val) => new Wrapper(val);

Here you can use the Identity function to extract the value (map the identity function)!

const wrappedValue = wrap('Get Functional');
wrappedValue.map(R.identity); //-> 'Get Functional'

A functor:

// fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B]
Wrapper.prototype.fmap = function (f) {
  return wrap(f(this.val));
};

a function that first opens the container, then applies the given function to its value, and finally closes the value back into a new container of the same type.

In essence, a functor is nothing more than a data structure that you can map functions over with the purpose of lifting values into a wrapper, modifying them, and then putting them back into a wrapper. It’s a design pattern that defines semantics for how fmap should work.

The purpose of having fmap return the same type (or wrap the result again into a container of the same type) is so you can continue chaining operations.

const two = wrap(2);
let meow = two.fmap(plus3).fmap(R.tap(infoLogger));

Here R.tap is needed because otherwise meow would be a wrap containing the undefined value, and not the value 5! Not convinced? Look again the fmap function implementation.

Functors properties:

  • Must be side effect free
  • Must be composable: the composition of a function applied to fmap should be exactly the same as chaining fmap functions together.
// this
two.fmap(plus3).fmap(R.tap(infoLogger));
// is the same as
two.fmap(R.compose(plus3, R.tap(infoLogger)));

Their practical purpose is to create a context or an abstraction that allows you to securely manipulate and apply operations to values without changing any original values

Functors are not expected to know how to handle cases with null data => monads

Monads

The purpose of monads is to provide an abstraction over some resource—whether it’s a simple value, a DOM element, an event, or an AJAX call—so that you can safely process the data contained within it. In this respect, you can classify jQuery as a DOM monad:

$('#student-info').fadeIn(3000).text(student.fullname());

This code behaves like a monad because jQuery is taking charge of applying the fadeIn and text transformations safely. If the student-info panel doesn’t exist, applying methods to the empty jQuery object will fail gracefully rather than throw exceptions.

Like functors, it’s a design pattern used to describe computations as a sequence of steps without having any knowledge of the value they’re operating on.

Monad: Provides the abstract interface for monadic operations ■ Monadic type: A particular concrete implementation of this interface

Monadic types have different implementations describing different behaviours (fmap). But they all share the same interface which has the following properties:

  • Type constructor: Creates monadic types (similar to the Wrapper constructor).
  • Unit function: Inserts a value of a certain type into a monadic structure (similar to the wrap function. When implemented in the monad, though, this function is called of.
  • Bind function: Chains operations together (this is a functor’s fmap, also known as flatMap ). From here on, I’ll use the name map , for short. By the way, this bind function has nothing to do with the function-binding concept (context).
  • Join operation: Flattens layers of monadic structures into one. This is especially important when you’re composing multiple monad-returning functions.
class Wrapper {
  constructor(value) {
    this._value = value;
  }

  static of(a) {
    return new Wrapper(a);
  }

  map(f) {
    return Wrapper.of(f(this.value));
  }

  join() {
    if(!(this.value instanceof Wrapper)) {
      return this;
    }
    return this.value.join();
  }
 
  toString() {
    return `Wrapper (${this.value})`; 
  }
}

Join example

Wrapper.of(Wrapper.of(Wrapper.of('Get Functional'))).join();
//-> Wrapper('Get Functional')

A monad is abstract stuff, let's see some concrete examples.

Maybe

As the Either monad has the following characteristics:

  • Wall off impurity
  • Consolidate null-check logic
  • Avoid exception throwing
  • Support compositionally of functions
  • Centralize logic for providing default values

Has two concrete subtypes:

  • Just(value): container of a value
  • Nothing(): container of no value or a failure
class Maybe {
  static just(a) {
    return new Just(a);
  }
  
  static nothing() {
    return new Nothing();
  }
  
  static fromNullable(a) {
    return a !== null ? just(a) : nothing();
  }

  static of(a) {
    return just(a);
  }
  
  get isNothing() {
    return false;
  }

  get isJust() {
    return false;
  }
}

class Just extends Maybe {
  constructor(value) {
    super();
    this._value = value;
  }

  get value() {
    return this._value;
  }

  map(f) {
    return of(f(this.value));
  }

  getOrElse() {
    return this.value;
  }
  
  filter(f) {
    Maybe.fromNullable(f(this.value) ? this.value : null);
  }

  get isJust() {
    return true;
  }

  toString () {
    return `Maybe.Just(${this.value})`;
  }
}

class Nothing extends Maybe {
  map(f) {
    return this;
  }

  get value() {
    throw new TypeError('Can't extract the value of a Nothing.');
  }
  
  getOrElse(other) {
    return other;
  }
  
  filter() {
    return this.value;
  }
 
  get isNothing() {
    return true;
  }
  
  toString() {
    return 'Maybe.Nothing';
  }
}

> This monad is frequently used with calls that contain uncertainty: querying a database, looking up values in a collection, requesting data from the server, and so on.

``` javascript
// safeFindObject :: DB -> String -> Maybe
const safeFindObject = R.curry(function(db, id) {
  return Maybe.fromNullable(find(db, id));
});
// safeFindStudent :: String -> Maybe(Student)
const safeFindStudent = safeFindObject(DB('student'));
const address = safeFindStudent('444-44-4444').map(R.prop('address'));
address; //-> Just(Address(...)) or Nothing

Imperative vs functional:

// imperative

function getCountry(student) {
  let school = student.school();
  if(school !== null) {
    let addr = school.address();
    if(addr !== null) {
      return addr.country();
    }
  } 
  return 'Country does not exist!';
}

// functional
const getCountry = (student) => student
  .map(R.prop('school'))
  .map(R.prop('address'))
  .map(R.prop('country'))
  .getOrElse('Country does not exist!');
  
const country = R.compose(getCountry, safeFindStudent);

We can write an utility function which lifts other function to return and work on monads:

const lift = R.curry(function (f, value) {
  return Maybe.fromNullable(value).map(f);
});

// function which down not work on monads...
const findObject = R.curry(function(db, id) {
  return find(db, id);
});

// lifted: works on monads
const safeFindObject = R.compose(lift, findObject);
safeFindObject(DB('student'), '444-44-4444');

Maybe manages null values, but what if we want to know the cause of the failure?

Either

Either is a structure that represents a logical separation between two values a and b that would never occur at the same time. This type models two cases:

Left(a): Contains a possible error message or throwable exception object ■ Right(b): Contains a successful value

A common use of Either is to hold the results of a computation that may fail to provide additional information as to what the failure is.

class Either {
  constructor(value) {
    this._value = value;
  }
  
  get value() {
    return this._value;
  }

  static left(a) {
    return new Left(a);
  }
  
  static right(a) {
    return new Right(a);
  }
  
  static fromNullable(val) {
    return val !== null ? right(val) : left(val);
  }
  
  static of(a){
    return right(a);
  }
}

class Left extends Either {
  map(_) {
    return this; // noop
  }

  get value() {
    throw new TypeError('Can't extract the value of a Left(a).');
  }
  
  getOrElse(other) {
    return other;
  }

  orElse(f) {
    return f(this.value);
  }

  chain(f) {
    return this;
  }

  getOrElseThrow(a) {
    throw new Error(a);
  }

  filter(f) {
    return this;
  }
  
  toString() {
    return `Either.Left(${this.value})`;
  }
}

class Right extends Either {
  map(f) {
    return Either.of(f(this.value));
  }

  getOrElse(other) {
    return this.value;
  }
  
  orElse() {
    return this;
  }

  chain(f) {
    return f(this.value);
  }

  getOrElseThrow(_) {
    return this.value;
  }

  filter(f) {
    return Either.fromNullable(f(this.value) ? this.value : null);
  }

  toString() {
    return `Either.Right(${this.value})`;
  }
}
  

If findStudent doesn’t return an object, you can use the orElse function on the Left operand to log the error:

const errorLogger = _.partial(logger, 'console', 'basic', 'MyErrorLogger', 'ERROR');
findStudent('444-44-4444').orElse(errorLogger);

Functional programming leads to avoiding ever having to throw exceptions. Instead, you can use this monad for lazy exception throwing by storing the exception object into the left structure.

IO

Js interaction with the DOM is impure. Reading and writing DOM elements can lead to different results depending on timing, order of executions etc...

It' impossible to get rid of such impurity, but we can wrap it in a monad structure IO which will behave immutabily in respect to our application.

class IO {
  constructor(effect) {
    if (!_.isFunction(effect)) {
      throw 'IO Usage: function required';
    }
    this.effect = effect;
  }

  static of(a) {
    return new IO( () => a );
  }

  static from(fn) {
    return new IO(fn);
  }

  map(fn) {
    var self = this;
    return new IO(function () {
      return fn(self.effect());
    });
  }

  chain(fn) {
    return fn(this.effect());
  }

  run() {
    return this.effect();
  }
}

With this monad, you can chain together any DOM operations to be executed in a single “pseudo” referentially transparent operation and ensure that side effect–causing functions don’t run out of order or between calls.

const readDom = _.partial(read, document);
const writeDom = _.partial(write, document);

// <div id="student-name">alonzo church</div>
const changeToStartCase =
  IO.from(readDom('student-name')).
     map(_.startCase).
     map(writeDom('student-name'));

Just like any other monad, the output from map is the monad itself, an instance of IO, which means at this stage nothing has been executed yet. What you have here is a declarative description of an IO operation. Finally, let’s run this code:

changeToStartCase.run();

Chain and composition

// validLength :: Number, String -> Boolean
const validLength = (len, str) => str.length === len;

// checkLengthSsn :: String -> Either(String)
const checkLengthSsn = function (ssn) {
  return Either.of(ssn).filter(_.bind(validLength, undefined, 9))
    .getOrElseThrow(`Input: ${ssn} is not a valid SSN number`);
};

// safeFindObject :: Store, string -> Either(Object)
const safeFindObject = R.curry(function (db, id) {
  return Either.fromNullable(find(db, id))
    .getOrElseThrow(`Object not found with ID: ${id}`);
  });

// finStudent :: String -> Either(Student)
const findStudent = safeFindObject(DB('students'));

// csv :: Array => String
const csv = arr => arr.join(',');

const debugLog = _.partial(logger, 'console', 'basic', 'Monad Example', 'TRACE');
const errorLog = _.partial(logger, 'console', 'basic', 'Monad Example', 'ERROR');
const trace = R.curry((msg, val)=> debugLog(msg + ':' + val));

const showStudent = (ssn) =>
  Maybe.fromNullable(ssn)
    .map (cleanInput)
    .chain(checkLengthSsn)
    .chain(findStudent)
    .map (R.props(['ssn', 'firstname', 'lastname']))
    .map (csv)
    .map (append('#student-info'));
    
showStudent('444-44-4444').orElse(errorLog);

The chain method is nothing more than a shortcut to avoid having to use join after map to flatten the layers resulting from combining monad-returning functions. Like map , chain applies a function to the data without wrapping the result back into the monad type.

This was chaining, in order to compose monads, we need some OOP2functional transforms (lifting):

// map :: (ObjectA -> ObjectB), Monad -> Monad[ObjectB]
const map = R.curry(function (f, container) {
  return container.map(f);
});

// chain :: (ObjectA -> ObjectB), M -> ObjectB
const chain = R.curry(function (f, container) {
  return container.chain(f);
});

You can use these functions to inject monads into a compose expression.

const showStudent = R.compose(
  R.tap(trace('Student added to HTML page'))
  map(append('#student-info')),
  R.tap(trace('Student info converted to CSV')),
  map(csv),
  map(R.props(['ssn', 'firstname', 'lastname'])),
  R.tap(trace('Record fetched successfully!')),
  chain(findStudent),
  R.tap(trace('Input was valid')),
  chain(checkLengthSsn),
  lift(cleanInput));

Time to add the IO monad to manage DOM writing:

const liftIO = function (val) {
  return IO.of(val);
};

const showStudent = R.compose(
  map(append('#student-info')),
  liftIO,
  map(csv),
  map(R.props(['ssn', 'firstname', 'lastname'])),
  chain(findStudent),
  chain(checkLengthSsn),
  lift(cleanInput));

// Because you’ve lifted the data into an IO monad, you need to call its run function
// for the data that’s lazily contained within it (in its closure)
// to be flushed out to the screen
showStudent(studentId).run(); //-> 444-44-4444, Alonzo, Church

Whereas composition controls program flow, monads control data flow. Both are possibly the most important concepts in the functional programming ecosystem.

Unit Tests

QUnit.test('Functional Combinator: fork', function (assert) {
  const timesTwo = fork((x) => x + x, R.identity, R.identity);
  assert.equal(timesTwo(1), 2);
  assert.equal(timesTwo(2), 4);
});

Testing with a simple function is sufficient, because combinators are completely agnostic when it comes to the arguments provided.

Property based testing

  • “If the student’s average is 90 or above, the student is awarded an A.” (imperative-case clauses)
  • “Only an average of 90 or above will award the student an A.” (generic, universal clauses)

In the second sentence we can derive that a numerical average above 90 will result in A, but ALSO that no other numerical average should result in A.

A property-based test makes a statement about what the output of a function should be when executed against a definite set of inputs.

jscheck: https://github.com/douglascrockford/JSCheck

Proving the properties of a function is done by generating a large number of random test cases aimed at rigorously exercising all possible output paths of your function. The main advantage of using a tool like JSC heck is that its algorithm gener ates abnormal datasets to test with. Some of the edge cases it generates would most likely be overlooked if you had to manually write them

JSC.claim(name, predicate, specifiers, classifier)
  • name: test description
  • predicate: function that returns a verdict of true when the claim is satisfied or false otherwise
  • specifiers: array describing the type of the input parameters and the specification with which to generate random datasets
  • classifier: function associated with each test case that can be used to reject non-applicable cases

Claims are passed into JSCheck.check to run random test cases. This library wraps creating a claim and feeding it into the engine in a single call to JSCheck.test, so you’ll use this shortcut method.

Example: test that "Only an average of 90 or above will award the student an A."

JSC.clear();

JSC.on_report((str) => console.log(str));
JSC.test(
  'Compute Average Grade',
  function (verdict, grades, grade) {
    return verdict(computeAverageGrade(grades) === grade);
  },
  [
    JSC.array(JSC.integer(20), JSC.number(90,100)),
    'A'
  ],
  function (grades, grade) {
    return 'Testing for an ' + grade + ' on grades: ' + grades;
  }
);

Performance

Memoization

Imperative way: cache layer:

function cachedFn (cache, fn, args) {
  let key = fn.name + JSON.stringify(args);
  if(contains(cache, key)) {
    return get(cache, key);
  }
  else {
    let result = fn.apply(this, args);
    put(cache, key, result);
    return result;
  }
}

It has side effects: the cache global shared object.

Function.prototype.memoized = function () {
  let key = JSON.stringify(arguments);
  this._cache = this._cache || {};
  this._cache[key] = this._cache[key] || this.apply(this, arguments);
  return this._cache[key];
};

Function.prototype.memoize = function () {
  let fn = this;
  if (fn.length === 0 || fn.length > 1) {
    return fn;
  }
  return function () {
    return fn.memoized.apply(fn, arguments);
  };
};

The more fine-grained your code is, the greater the benefits obtained via memoization will be. Each and every function’s internal caching mechanism is playing a role in speeding up evaluation of your programs—there’s more surface contact, if you will.

Tail Call Recursion Optimization

Not optimized:

const factorial = (n) => (n === 0)
  ? 1
  : (n * factorial(n - 1));

Optimized (uses an accumulator: the result is accumulated in the second argument of the recursive function):

const factorial = (n, current = 1) => (n === 1)
  ? current
  : factorial(n - 1, n * current);

TCO only occur when the last act of the recursive solution is to invoke another function (typically itself); this last invocation is said to be in tail position (hence the name).

Having a function call as the last thing to run in a recursive function allows the JavaScript runtime to realize it doesn’t need to hold on to the current stack frame any longer, because it doesn’t have any more work to do; so it discards the stack frame. In most cases, you achieve this by transferring all the necessary state from one function context to the next as part of the function’s arguments. This way, the recursive iteration tends to happen with a new frame every time, recycled from the previous one, instead of frames stacked one after the other.

factorial(4)
4 * factorial(3)
  4 * 3 * factorial(2)
    4 * 3 * 2 * factorial(1)
      4 * 3 * 2 * 1 * factorial(0)
        4 * 3 * 2 * 1 * 1
      4 * 3 * 2 * 1
    4 * 3 * 2
  4 * 6
return 24

factorial(4)
  factorial(3, 4)
  factorial(2, 12)
  factorial(1, 24)
  factorial(0, 24)
  return 24
return 24

If tail optimization is not supported...

Trampoline

Is just a way to control the execution if the recursive function in an interative way. The purpose is to avoid a stack overflow (without TCO every time a recursion happens, a new stack frame is added on top!)

// wrap the recursive function ina a function
function repeat(operation, num) {
  return function() {
    if (num <= 0) return
    operation()
    return repeat(operation, --num)
  }
}

// The returned function is the next step of recursive execution and the trampoline is a mechanism to execute these steps in a controlled and iterative fashion in the while loop:

function trampoline(fn) {
  while(fn && typeof fn === 'function') {
    fn = fn()
  }
}

const myRecursiveFunction = function(operation, num) {
  trampoline(function() {
    return repeat(operation, num)
  })
}

Reactive Programming

Road to Promises

Deal with events and async stuff.

Callbacks => inversion of control: “Don’t call me, I’ll call you.” Nested callbacks => Callback hell

Possible solution: continuation passing style ( CPS ) Just define all calbacks as separate functions or lambda expressions, more linear code, no closures => more efficiency in terms of context stack

var _selector = document.querySelector;
_selector('#search-button').addEventListener('click', handleClickEvent);
var processGrades = function (grades) {
  // ... process list of grades for this student...
};
var handleMouseMovement = () = getJSON(`/students/${info.ssn}/grades`, processGrades);
var showStudent = function (info) {
  _selector('#student-info').innerHTML = info;
  _selector('#student-info').addEventListener('mouseover', handleMouseMovement);
};
var handleError = error => console.log('Error occurred' + error.message);
var handleClickEvent = function (event) {
  event.preventDefault();
  let ssn = _selector('#student-ssn').value;
  if(!ssn) {
    alert('Valid SSN needed!');
    return;
  }
  else {
    getJSON(`/students/${ssn}`, showStudent).fail(handleError);
  }
};

Best approach: Promises: sounds like monads wrapping a behaviour, are thenable (chainable).

The Promise object defines a then method (analogous to a functor’s fmap ), which applies an operation on a value returned in a promise and closes it back into a Promise.

getJSON('/students')
  .then(hide('spinner'))
  .then(R.filter(s => s.address.country == 'US'))
  .then(R.sortBy(R.prop('ssn')))
  .then(R.map(student => {
    return getJSON('/grades?ssn=' + student.ssn)
      .then(R.compose(Math.ceil, forkJoin(R.divide, R.sum, R.length)))
      .then(grade => 
        IO.of(R.merge(student,{'grade': grade}))
          .map(R.props(['ssn', 'firstname','lastname', 'grade']))
          .map(csv)
          .map(append('#student-info')).run())
        );
      }))
  .catch(function(error) {
    console.log('Error occurred: ' + error.message);
  });

This code fetches each student and appends them to the DOM one at a time. But by serializing operations to fetch grades, you’re losing some precious time. Promise also has the ability to take advantage of the browser’s multiple connections to fetch multiple items at once => Promise.all

const average = R.compose(Math.ceil, forkJoin(R.divide, R.sum, R.length));
getJSON('/students')
  .then(hide('spinner'))
  .then(R.map(student => '/grades?ssn=' + student.ssn))
  .then(gradeUrls => Promise.all(R.map(getJSON, gradeUrls)))
  .then(R.map(average))
  .then(average)
  .then(grade => IO.of(grade).map(console.log).run())
  .catch(error => console.log('Error occurred: ' + error.message));

Using Promise.all takes advantage of the browser’s ability to download multiple things at once. The resulting promise resolves as soon as all promises in the iterable argument have resolved.

That was chaining, bro'.

With some helper functions we can also compose or pipe stuff:

// fetchStudentDBAsync :: DB -> String -> Promise(Student)
const fetchStudentDBAsync = R.curry(function (db, ssn) {
  return find(db, ssn);
});
// findStudentAsync :: String -> Promise
const findStudentAsync = fetchStudentDBAsync(db);
// then :: f -> Thenable -> Thenable
const then = R.curry(function (f, thenable) {
  return thenable.then(f);
});
// catchP :: f -> Promise -> Promise
const catchP = R.curry(function (f, promise) {
  return promise.catch(f);
});
// errorLog :: Error -> void
const errorLog = _.partial(logger, 'console', 'basic', 'ShowStudentAsync', 'ERROR');

const showStudentAsync = R.compose(
  catchP(errorLog),
  then(append('#student-info')),
  then(csv),
  then(R.props(['ssn', 'firstname', 'lastname'])),
  chain(findStudentAsync),
  map(checkLengthSsn),
  lift(cleanInput));

The promise in this case acts as a gateway into the asynchronous part. It’s also declarative in that nothing in this program reveals the internal behavior of the asynchronous nature of the function or that callbacks are being used.

Generators

Pause/Resume -> lazy stuff.

Specify rules that govern how data should be created => Generators

R.range(1, Infinity).take(3);

The above stuff will fail because in js functions are evaluated eagerly, not lazily! In haskell the same think would work.

We can solve that using generators:

function *range(start = 0, finish = Number.POSITIVE_INFINITY) {
  for(let i = start; i < finish; i++) {
    yield i;
  }
}

const num = range(1);
num.next().value; //-> 1
num.next().value; //-> 2
num.next().value; //-> 3

// or iterating over a generator

for (let n of range(1)) {
  console.log(n);
  if(n === threshold) {
    break;
  }
}// -> 1,2,3,...

Let's implement the take function, then:

function take(amount, generator) {
  let result = [];
  for (let n of generator) {
    result.push(n);
    if(n === amount) {
      break;
    }
  }
  return result;
}
take(3, range(1, Infinity)); //-> [1, 2, 3]

We can have recursion with generators, so we can flatten nested structures!

function* TreeTraversal(node) {
  yield node.value;
  if (node.hasChildren()) {
    for(let child of node.children) {
      yield* TreeTraversal(child);
    }
  }
}

The generator returns an object with properties: value, done.

var iter = ['S', 't', 'r', 'e', 'a', 'm'][Symbol.iterator]();
iter.next().value; // S
iter.next().value; // t

// But even strings can be iterated over:

var iter = 'Stream'[Symbol.iterator]();
iter.next().value// -> S
iter.next().value// -> t

Symbol.iterator? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator

Observables

An observable is any data object that you can subscribe to.

A stream is a sequence of ordered events happening over time.

Rx.Observable.range(1, 3)
  .subscribe(
    x => console.log(`Next: ${x}`),
    err => console.log(`Error: ${err}`),
    () => console.log('Completed')
);

// Next: 1
// Next: 2
// Next: 3
// Completed

Rx.Observable wraps or lifts any observable object so that you can map and apply different functions to transform the observed values into the desired output. Hence, it’s a monad. It implements the equivalent of the minimal monadic interface ( map , of , and join ) as well as many methods specific to stream manipulation.

Imperative

document.querySelector('#student-ssn')
  .addEventListener('change', function (event) {
    let value = event.target.value;
    value = value.replace(/^\s*|\-|\s*$/g, '');
    console.log(value.length !== 9 ? 'Invalid' : 'Valid'));
});
//-> 444 Invalid
//-> 444-44-4444 Valid

Reactive

Rx.Observable.fromEvent(document.querySelector('#student-ssn'), 'change')
  .map(x => x.target.value)
  .map(cleanInput)
  .map(checkLengthSsn)
  .subscribe(ssn => ssn.isRight ? console.log('Valid') : console.log('Invalid'));

Rx JS can convert any Promises/A+–compliant object into an observable sequence.

Rx.Observable.fromPromise(getJSON('/students'))
  .map(R.sortBy(R.compose(R.toLower, R.prop('firstname'))))
  .flatMapLatest(student => Rx.Observable.from(student))
  .filter(R.pathEq(['address', 'country'], 'US'))
  .subscribe(
    student => console.log(student.fullname),
    err => console.log(err)
  );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment