run[If]
and apply
are functions that can help you craft easy-to-read code.
run
forwards the first argument to the callback, and returns the result.
// Calculate the area.
const area = run(
getSize(),
({ width, height }) => width * height
);
width
and height
are neatly confined to the callback. The alternative without run
has those variables overstay their welcome, polluting the scope:
// Calculate the area.
const { width, height } = getSize();
const area = width * height;
apply
forwards the first argument to the callback ‒ just like run
does ‒ but returns that argument instead of the result.
// Remove the item from the selection.
setState(previousSelection => apply(
new Set(previousSelection),
selection => selection.delete(item)
));
The alternative without apply
is more wordy:
// Remove the item from the selection.
setState(previousSelection => {
const selection = new Set(previousSelection);
selection.delete(item);
return selection;
});
runIf
skips the callback if the first argument is nullish, and behaves the same as run
otherwise. runIf
is to run
as the ?.
operator is to the .
operator.
// Parse the timestamp (if any).
const timestamp = runIf(
response.data.timestamp,
Date.parse
);
Alternatives include this:
// Parse the timestamp (if any).
let { timestamp } = response.data;
if (timestamp != undefined) {
timestamp = Date.parse(timestamp);
}
And this*:
// Parse the timestamp (if any).
let timestamp = Date.parse(response.data.timestamp);
if (isNaN(timestamp)) {
timestamp = undefined;
}
And this dangerous** one:
// Parse the timestamp (if any).
const timestamp = response.data.timestamp
&& Date.parse(response.data.timestamp);
[*] In the alternative with isNaN
, invalid timestamps (such as 'invalid'
) are indistinguishable from missing timestamps. This is likely unexpected behaviour.
[**] In the alternative with the &&
operator, Date.parse
is skipped not only if the timestamp is undefined
but also if it is ''
(or any other falsy value). This too is likely unexpected.
You can combine runIf
and the ??
operator to provide a fallback used if the callback is skipped.
return runIf(event.data, JSON.parse) ?? {};
Install ruply
using npm or Yarn and import the functions:
import { run, apply, runIf } from 'ruply';
These are simplified implementations of run[If]
and apply
(without support for promises or chains):
function run(value, callback) {
return callback(value);
}
function apply(value, callback) {
callback(value);
return value;
}
function runIf(value, callback) {
return value != null ? callback(value) : value;
}
These functions are designed to help your code better convey your intentions to the reader.
Non-goals include:
- producing the shortest possible code, and
- producing clever code.
Use run[If]
and apply
in situations where you feel they help the reader of your code.
let available = 0;
function generateID() {
return available++;
}
run
scopes the variable, protecting it from accidental access.
const generateID = run(0, available => () => available++);
logger.log(`Received ${bundles.reduce(
(sum, { messages }) => sum + messages.length, 0
)} message(s)`);
run
moves the summing logic outside the template literal.
run(
bundles.reduce(
(sum, { messages }) => sum + messages.length, 0
),
messageCount =>
logger.log(`Received ${messageCount} message(s)`)
);
if (data.has(index)) {
return data.get(index);
}
// If the buffer does not exist, allocate it…
const buffer = Buffer.alloc(size);
// …and store it in the map.
data.set(index, buffer);
return buffer;
apply
rearranges the pieces, producing code closer to human speech.
return data.get(index)
?? apply(
// If the buffer does not exist, allocate it…
Buffer.alloc(size),
// …and store it in the map.
buffer => data.set(index, buffer)
);
const start = performance.now();
const result = await queryDatabase();
console.log(`${performance.now() - start} ms`);
return result;
apply
removes the need for the short-lived variable, and places return
and queryDatabase()
closer together.
const start = performance.now();
return apply(
queryDatabase(),
() => console.log(`${performance.now() - start} ms`)
);
The await
keyword is optional, as apply
is promise-aware.
const token =
request.headers.authorization != undefined
? /^Bearer\s+(.*)$/.exec(
request.headers.authorization
)?.[1]
: undefined;
runIf
cuts out the undefined
check (for cases where the header is missing) as well as the ?.
operator (for cases where the regular expression does not match).
const token = runIf(
request.headers.authorization,
headerValue => /^Bearer\s+(.*)$/.exec(headerValue),
([, token]) => token
);
If you prefer keeping the ?.
operator, then you should.
const token = runIf(
request.headers.authorization,
headerValue => /^Bearer\s+(.*)$/.exec(headerValue)
)?.[1];
const value = await cache.get(key);
// Disregard the value if it has expired.
if (value == null || value.expiration < Date.now()) {
return null;
}
return value;
runIf
cuts out the null
check.
return runIf(
cache.get(key),
// Disregard the value if it has expired.
value => value.expiration < Date.now() ? null : value
);
The await
keyword is optional, as runIf
is promise-aware.
const orders = new Map();
function memoGetOrder(id) {
if (orders.has(id)) {
return orders.get(id);
}
const order = getOrder(id);
orders.set(id, order);
return order;
}
run
scopes the map, while apply
rearranges the pieces of the function body to match the human speech equivalent.
const memoGetOrder = run(new Map(), orders =>
id => orders.get(id)
?? apply(getOrder(id), order => orders.set(id, order))
);
/ɹʌˈplaɪ/
Like run apply.
Copyright (c) 2020, 2021 Pimm "de Chinchilla" Hogeling
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. in no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.