ChainContainer

ChainContainer

Simple monad like wrapper.

See the util.chain method for more.

Very helpful for taking values and then progressively working on them, instead of continually wrapping deeper in method calls.

Calling chain(3) - gives an object you can then chain calls against:

  • .close() - gets the value of the current chain
  • .chain(function) - where it is passed the value, and returns a new Chain with that value.
  • .errorHandler(fn) - custom function called if an error is ever thrown
  • .debug() - console.logs the current value, and continues the chain with that value

Along with methods that can iterate on each element, assuming the value in the chain is an Array.

  • .chainMap(function) - where it calls the .map on value, and applies the function on every item in the array, storing the result from the function.
    (Useful for changing values without changing the original object))
  • .chainForEach(function) - where calls .forEach on value, and applies the function on every item, without storing the result from the function.
    (Useful for changing objects in-place)
  • .chainFlatMap(function) - where it calls .flatMap on value, and applies the function on every item, flattening the results.
    (Useful for expanding an array based on values in the array)
  • .chainFilter(function) - where it calls .filter on value, using the function on every item, keeping the item in the list if the function returns true.
    (Useful for removing items from an array)
  • .chainReduce(function, initialValue) - where it calls .reduce on value, and reduces the value to a single result.
    (Useful for reducing the array to a single value
    - like a concatenated string or sum total)

There may be times you want to run side effects, or replace the value entirely. (This isn't common, but may be useful on occasion)

  • .execute(function) - where it calls a function, but doesn't pass on the result.
    (This is useful for side-effects, like writing to files)
  • .replace(value) - replaces the value in the chain with a literal value, regardless of the previous value.

For example:

addTwo = (value) => value + 2;

//-- we can always get the value
utils.chain(3).close();  // 3

but this is much easier if we continue to chain it

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(addTwo) // (3 + 2)
  .chain(addTwo) // (5 + 2)
  .debug() // consoles 7 and passes the value along
  // define a function inline
  .chain((value) => value + 3) // (7 + 3)
  .close()

// consoles out value `7`
// returns value 10

Note that we can also map against values in the array

initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
initializeArray(3); // [0, 1, 2]

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(initializeArray) // [0, 1, 2]
  .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
  .chainMap(addTwo)
  .close();
// [4, 5, 6]

Chain to log results while transforming values

results = [{ userId: 'abc123' }, { userId: 'xyz987' }];

activeUsers = chain(results)
 .chainMap((record) => users.get(record.userId))
 .chainForEach(record => record.status =  'active')
 .chain(records => d3.csv.format(records))
 .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
 .close()

Or even combine with other utility methods

badStr = 'I%20am%20the%20very%20model%20of%20a%20modern%20Major'
 + '-General%0AI\'ve%20information%20vegetable%2C%20animal%2C%20'
 + 'and%20mineral%0AI%20know%20the%20kings%20of%20England%2C%20'
 + 'and%20I%20quote%20the%20fights%0AHistorical%0AFrom%20Marath'
 + 'on%20to%20Waterloo%2C%20in%20order%20categorical';

chain(badStr)
    .chain(decodeURIComponent)
    .chain(v => v.split('\n'))
    // .debug()                  // check the values along the way
    .chainMap(line => ({ line, length: line.length }))
    .chain(values => utils.table(values).render());

this can be more legible than the normal way to write this,
especially if you need to troubleshoot the value halfway through.

utils.table(
 decodeURIComponent(badStr)
   .split('\n')
   .map(line => ({ line, length: line.length }))
).render()

and it renders out a lovely table like this:

line length
I am the very model of a modern Major-General 45
I've information vegetable, animal, and mineral 47
I know the kings of England, and I quote the fights 51
Historical 10
From Marathon to Waterloo, in order categorical 47

Constructor

new ChainContainer(value)

Constructor that creates a new Container to pass along the chain.

If the context is null, then we assume we are binding the container to the current iJavaScript cell.

Parameters:
Name Type Description
value any

the value to use along the chain

Members

value :any

Value this container stores, can be anythhing.

Also access through ChainContainer#close

Type:
  • any

Methods

chain(functor) → {ChainContainer}

See:

Creates a new chain container holding the value returned from functor(this.value)

value = 2;
plus2 = (value) => value + 2;

chain(value)
 .chain(plus2) // 2 + 2
 .chain(plus2) // 4 + 2
 .debug()
 .chain(plu2)  // 6 + 2
 .close();

// 6
// 8

Or even combine with other utility methods

badStr = 'I%20am%20the%20very%20model%20of%20a%20modern%20Major'
 + '-General%0AI\'ve%20information%20vegetable%2C%20animal%2C%20'
 + 'and%20mineral%0AI%20know%20the%20kings%20of%20England%2C%20'
 + 'and%20I%20quote%20the%20fights%0AHistorical%0AFrom%20Marath'
 + 'on%20to%20Waterloo%2C%20in%20order%20categorical';

chain(badStr)
    .chain(decodeURIComponent)
    .chain(v => v.split('\n'))
    // .debug()                  // check the values along the way
    .chainMap(line => ({ line, length: line.length }))
    .chain(values => utils.table(values).render());

and it renders out a lovely table like this:

line length
I am the very model of a modern Major-General 45
I've information vegetable, animal, and mineral 47
I know the kings of England, and I quote the fights 51
Historical 10
From Marathon to Waterloo, in order categorical 47
Parameters:
Name Type Description
functor function

the function given the value, returning a transformed value

Returns:
  • container with the results from functor(this.value)
Type
ChainContainer

chainFilter(fn) → {ChainContainer}

Assuming that value is an array, this maps fn to filter the results in the array.

chain([1,2,3,4])
   .chainFilter((value) => value < 3)
   .close();
// [1, 2]
Parameters:
Name Type Description
fn function

Function accepting a value and returning whether it should be included (true) or not (false)

Returns:
Type
ChainContainer

chainFlatMap(fn) → {ChainContainer}

See:

Assuming that value is an array, performs a javaScript flatMap against the results.

This can be very helpful in expanding the list of items in an array, or removing items from an array.

// expanding size of the array
initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
initializeArray(3); // [0, 1, 2]

utils.chain([1, 2, 3, 4])
 .chainFlatMap(initializeArray)
 .close();

// [1, 1, 2, 1, 2, 3, 1, 2, 3, 4];

or similar to chainFilter

// reducing the size of the array
filterOdd = (value) => value % 2 === 0 ? [value] : [];
filterOdd(2); // [2]
filterOdd(1); // []

chain([1, 2, 3, 4, 5])
 .chainFlatMap(filterOdd)
 .close();

// [2, 4];
Parameters:
Name Type Description
fn function

function that can either return a value or array of values.

Returns:
Type
ChainContainer

chainForEach(fn) → {ChainContainer}

Assuming that value is an array, performs a javaScript forEach against the results.

This will run the passed function against every element in the result, without replacing the element with the returned value and makes inline editing simpler.

list = [{ first: 'john', last: 'doe' }, { first: 'jane', last: 'doe' }];
utils.chain(list)
 .mapForEach((entry) => entry.name = `${entry.first} ${entry.last})
 .close();
// [{ first: 'john', last: 'doe', name: 'john doe' }, { first: 'jane', last: 'doe', name: 'jane doe' }]

This is in contrast to chainMap, that replaces the element with the value returned.

Parameters:
Name Type Description
fn function

function to execute on each element

Returns:
  • chainable container
Type
ChainContainer

chainMap(fn) → {ChainContainer}

Assuming that value is an array, this does a javascript array.map and applies fn to every value in the array.

initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
initializeArray(3); // [0, 1, 2]

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(initializeArray) // [0, 1, 2]
  .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
  .chainMap(addTwo)
  .close();

// [4, 5, 6]

This is in contrast to chainForEach

Parameters:
Name Type Description
fn function

applies function under every index of this.value

Returns:
Type
ChainContainer

chainReduce(fn, initialValue) → {ChainContainer}

Assuming that the value is an array, performs a reduce using fn and initialValue

initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
initializeArray(3); // [0, 1, 2]

addTwo = (value) => value + 2;
addTwo(3); // 5

utils.chain(3)
  .chain(initializeArray) // [0, 1, 2]
  .chainMap(addTwo) // [2, 3, 4] or [0 + 2, 1 + 2, 2 + 2]
  .debug()
  .chainReduce((result, value) => result + value, 0)
  .close();

// [2, 3, 4]
// 9
Parameters:
Name Type Description
fn function

reducer function

initialValue any

initial value passed to the reducer

Returns:
Type
ChainContainer

clone() → {ChainContainer}

Clones the value, and ties the output to this cell.

Example
customErrorHandler = (err) => console.error('Some custom warning');
initialChain = utils.chain(3)
  .errorHandler(customErrorHandler);

newChain = initialChain.clone()
  .chain(v => v + 2) // 3 + 2
  .close();

// 5
Returns:
  • new chain container to use in this cell.
Type
ChainContainer

close() → {any}

See:

Closes the chain and returns the current value.

Example
utils.chain(3)
  .chain(v => v + 2) // 3 + 2
  .close();

// 5
Returns:
Type
any

debug(fnopt) → {ChainContainer}

Console.logs the value, and returns the unmodified current value.

value = 2;
plus2 = (value) => value + 2;

chain(value)
 .chain(plus2) // 2 + 2
 .chain(plus2) // 4 + 2
 .debug()
 .chain(plu2)  // 6 + 2
 .debug((value) => console.log(value))
 .close();

// 6
// 8
// 8
Parameters:
Name Type Attributes Default Description
fn function <optional>
null

optional custom function

Returns:
  • the same value as current, regardless of the result from fn.
Type
ChainContainer

errorHandler(errorHandler)

Function to call if an error occurs anywhere on the chain.

someErrorOccurred = false;
flipSwitch = (err) => {
  console.log('custom handler');
  someErrorOccurred = true;
};
throwError = () => {
  throw Error('Custom Error');
};

chain(2)
  .errorHandler(flipSwitch)
  .chain((value) => value + 2)
  .chain(throwError);

// customHandler
// {
//   "name": "Error",
//   "message": "Custom Error"
// }
// /Users/proth/Documents/notebooks/jupyter-ijavascript-utils/src/chain.js:175
//     throw err;
//     ^
// 
// Error: Custom Error
//     at throwError (evalmachine.<anonymous>:7:9)
Parameters:
Name Type Description
errorHandler function

function that is passed the error caught

execute(fn) → {ChainContainer}

Applies a function against the current value, while not passing the results along the chain.

results = [{ userId: 'abc123' }, { userId: 'xyz987' }];

activeUsers = chain(results)
 .chainMap((record) => users.get(record.userId))
 .chainForEach(record => record.status =  'active')
 .chain(records => d3.csv.format(records))
 .execute(records => utils.file.writeFile('./log', d3.csv.format(records)))
 .close()
Parameters:
Name Type Description
fn function

function to execute against the current value

Returns:
Type
ChainContainer

replace(value) → {ChainContainer}

Normally, you will want to replace the value in the chain based on the current value.

This replaces the value regardless, and is rarely used.

Parameters:
Name Type Description
value any

new value in the chain.

Returns:
Type
ChainContainer