| name | functional |
| description | Functional JS elegance |
Jeremy Ashkenas JavaScript Philosophy
Applying the design philosophy of Jeremy Ashkenas (Backbone.js, Underscore.js, CoffeeScript) to JavaScript library and utility development. Use this when writing JavaScript utilities, designing APIs, building small libraries, or refactoring code toward functional elegance. Auto-invokes for .js files involving utility functions, library design, collection manipulation, or API surface design. Not for framework-heavy code (React, Angular), build tooling, or Node.js server infrastructure.
Core Philosophy
The Ashkenas Aesthetic
Readable over clever. Code is read far more than written. Optimize for the reader who encounters your code six months from now.
Small, focused functions. Each function does one thing. Name it for what it does. If the name is awkward, the function does too much.
Functional foundations. Prefer map, filter, reduce over loops. Treat data as immutable. Return new values rather than mutating.
Minimal API surface. The best library is the smallest one that solves the problem. Every public method is a promise to maintain.
Convention over configuration. Establish sensible defaults. Let users override when needed, not require configuration upfront.
Design Patterns
The Underscore Pattern: Collection-First Thinking
const getActiveUserEmails = (users) =>
users
.filter(user => user.active)
.map(user => user.email);
const getActiveUserEmails = (users) => {
const emails = [];
for (let i = 0; i < users.length; i++) {
if (users[i].active) {
emails.push(users[i].email);
}
}
return emails;
};
The Backbone Pattern: Events as Decoupling
class Model {
constructor() {
this._events = {};
this._attributes = {};
}
set(key, value) {
const prev = this._attributes[key];
this._attributes[key] = value;
if (prev !== value) {
this.trigger('change', key, value, prev);
this.trigger(`change:${key}`, value, prev);
}
return this;
}
on(event, callback) {
(this._events[event] ||= []).push(callback);
return this;
}
trigger(event, ...args) {
(this._events[event] || []).forEach(cb => cb(...args));
return this;
}
}
The CoffeeScript Pattern: Expression-Oriented
const classify = (score) =>
score >= 90 ? 'A' :
score >= 80 ? 'B' :
score >= 70 ? 'C' :
score >= 60 ? 'D' : 'F';
const formatUser = ({ name, email, role = 'member' }) =>
`${name} <${email}> (${role})`;
const paginate = (items, page = 1, perPage = 10) =>
items.slice((page - 1) * perPage, page * perPage);
API Design Principles
Chainable Methods
class Query {
constructor(data) {
this._data = data;
this._filters = [];
this._sort = null;
this._limit = null;
}
where(predicate) {
this._filters.push(predicate);
return this;
}
sortBy(key, direction = 'asc') {
this._sort = { key, direction };
return this;
}
limit(n) {
this._limit = n;
return this;
}
value() {
let result = this._data;
for (const filter of this._filters) {
result = result.filter(filter);
}
if (this._sort) {
const { key, direction } = .;
mult = direction === ? : -;
result = [...result].( a[key] > b[key] ? mult : -mult);
}
(.) {
result = result.(, .);
}
result;
}
}
topUsers = (users)
.( u.)
.( u. > )
.(, )
.()
.();
Sensible Defaults with Override
const fetch = (url, options = {}) => {
const config = {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
retries: 3,
...options
};
};
fetch('/api/users');
fetch('/api/users', { method: 'POST', body: data });
Predictable Naming
const isEmpty = (arr) => arr.length === 0;
const hasChildren = (node) => node.children?.length > 0;
const canEdit = (user, doc) => doc.ownerId === user.id;
const slugify = (str) => str.toLowerCase().replace(/\s+/g, '-');
const toArray = (value) => Array.isArray(value) ? value : [value];
const getName = (obj) => obj.name;
const getById = (id) => (arr) => arr.find(x => x.id === id);
Utility Patterns
Compose Small Functions
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const processUser = pipe(
validateEmail,
normalizePhone,
hashPassword,
saveToDatabase
);
const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);
Memoization for Expensive Operations
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key);
};
};
Debounce and Throttle
const debounce = (fn, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), wait);
};
};
const throttle = (fn, wait) => {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= wait) {
lastCall = now;
fn(...args);
}
};
};
Partial Application
const partial = (fn, ...presetArgs) =>
(...laterArgs) => fn(...presetArgs, ...laterArgs);
const greet = (greeting, name) => `${greeting}, ${name}!`;
const sayHello = partial(greet, 'Hello');
sayHello('World');
Code Review Checklist
When reviewing JavaScript utilities and libraries:
Function Design
API Surface
Collection Operations
Code Style
Anti-Patterns to Avoid
Over-Engineering
class UserServiceFactoryProvider {
createUserServiceFactory() {
return new UserServiceFactory();
}
}
const createUser = (data) => ({ id: uuid(), ...data, createdAt: Date.now() });
Premature Abstraction
const makeAdder = (x) => (y) => x + y;
const add5 = makeAdder(5);
add5(3);
Configuration Objects for Simple Cases
fetch({ url: '/users', method: 'GET', format: 'json' });
fetch('/users');
fetch('/users', { method: 'POST' });
When This Skill Applies
Use for:
- Writing utility functions
- Designing library APIs
- Collection manipulation
- Refactoring imperative code to functional
- Building small, focused modules
Skip for:
- React/Vue/Angular component patterns (use framework conventions)
- Node.js server code (use Node idioms)
- Build tooling (use tool conventions)
- Performance-critical hot paths (measure first)