Skip to content

Instantly share code, notes, and snippets.

@woudsma
Last active September 27, 2025 03:16
Show Gist options
  • Select an option

  • Save woudsma/df6faaed6cb90b4a20f5bbf1656ef412 to your computer and use it in GitHub Desktop.

Select an option

Save woudsma/df6faaed6cb90b4a20f5bbf1656ef412 to your computer and use it in GitHub Desktop.
When - a typed higher-order function

When

TypeScript

when lets you declaratively gate side-effects, computations, or constant values behind one or more conditions (booleans or predicates). If all conditions are truthy, your callback runs (or your constant is returned). Otherwise it returns false.

type Condition<Args extends unknown[]> = boolean | ((...args: Args) => boolean);
type Nested<T> = T | ReadonlyArray<Nested<T>>;

export const when =
  <Args extends unknown[], Result>(
    ...conditions: ReadonlyArray<Nested<Condition<Args>>>
  ) =>
  (callback: ((...args: Args) => Result) | Result) =>
  (...args: Args): Result | boolean => {
    const passes = (c: Nested<Condition<Args>>): boolean =>
      Array.isArray(c)
        ? c.every(passes)
        : typeof c === "function"
        ? c(...args)
        : Boolean(c);

    if (conditions.every(passes)) {
      if (typeof callback === "function")
        return (callback as (...a: Args) => Result)(...args);
      else if (callback) return callback as Result;
      else return true;
    } else return false;
  };
Usage:
  when(...conditions)(callbackOrValue)(...args) -> Result | boolean

Conditions:
   - booleans, or
   - predicates that receive the same args as the final call.
   - may be nested arbitrarily in arrays; everything is flattened.

Behavior:
   - If every condition is truthy, then:
       * if callback is a function: returns its return value (Result)
       * if callback is a non-function value: returns that value (Result)
       * if callback is falsy (rare): returns true
   - If any condition is falsy, returns false.

Types:
   - Args is the tuple of arguments passed at the final call.
   - Result is the return type (or the constant’s type).
   - Return type is `Result | boolean`.

Notes:
   - All conditions must be synchronous booleans/predicates.
   - Great for gating effects, “maybe” mapping, and permission checks.

Examples

when((x) => x > 0)((x) => x * 2)(5); // 10
when((x) => x > 0)((x) => x * 2)(0); // false

const multiplyIfPositive = when((x) => x > 0)((x) => x * 2);
multiplyIfPositive(10);  // 20
multiplyIfPositive(-10); // false
  • React handlers (gate an action by event + state)
function Button({ ready }: { ready: boolean }) {
  const handleClick = when<[React.MouseEvent], void>(
    (e) => !e.ctrlKey,   // don’t allow Ctrl+click
    () => ready          // only if component is “ready”
  )((e) => {
    // actual action
    doThing(e);
  });

  return <button onClick={handleClick}>Run</button>;
}
More examples
  • Permission checks with reusable guards
type User = { id: string; roles: string[]; active: boolean };

const isActive = (u: User) => u.active;
const hasRole = (role: string) => (u: User) => u.roles.includes(role);

// Return a boolean using a constant-true callback:
const canEdit = when<[User], true>(isActive, hasRole("editor"))(true);

if (canEdit(currentUser)) {
  // ...allowed path
} else {
  // ...deny
}
  • “Maybe” mapping: apply transform only when a rule holds
// Discount only when amount > 100
const maybeDiscount = when<[number], number>(
  (amount) => amount > 100
)((amount) => Math.round(amount * 0.9));

const amounts = [50, 120, 250];

const final = amounts.map((a) => maybeDiscount(a) === false ? a : discounted);
// -> [50, 108, 225]
  • Multiple, nested condition shapes.
    You can freely mix booleans and predicates in nested arrays; everything flattens.
type Req = { user?: User; scope?: string[] };

const isAuthed = (r: Req) => !!r.user;
const hasScope = (s: string) => (r: Req) => r.scope?.includes(s) ?? false;

const allowWrite = when<[Req], true>(
  [true, [isAuthed, [hasScope("write")]]]
)(true);

allowWrite({ user: { id: "1", roles: [], active: true }, scope: ["read", "write"] }); // true
allowWrite({ scope: ["write"] }); // false

Chain of thought: pass args into conditions, and when they're all true, execute the callback function with args.
Useful when you need to conditionally ignore certain function calls.

More complex example (snippet from my custom MIDI sequencer frontend):

export const isIndex = (index) => (args) => int(index) === int(firstArg(args));
export const isRegularSwitch = (args) => firstArg(args) % 2 === 0
export const isEncoderSwitch = (args) => firstArg(args) % 2 === 1;

const updateIsEditingControlChangeParams = when(
  showTrackControlChangeMenu,
  isEncoderSwitch,
  isIndex(CC_EDIT_MODE_SWITCH),
  !isMuteMode
)(() => setIsEditingControlChangeParams((e) => !e));

const updateMutedTracks = when(
  isRegularSwitch,
  isMuteMode
)((args) => {
  const switchIndex = int(firstArg(args)) / 2;

  setMutedTracks((e) =>
    e.includes(switchIndex)
      ? e.filter((v) => v !== switchIndex)
      : [...new Set(e.concat(switchIndex))]
  );
});

const updateIsFollowActive = when(
  showTrackSettingsMenu,
  !showTrackControlChangeMenu,
  trackSettingsMenuPage === trackSettingsMenuPages.TRACK_CONDITIONS
)((args) => {
  setIsFollowActive(getEncoderValue(args) === 1);
});

const events = [
  ["switch_pressed", updateIsEditingControlChangeParams],
  ["switch_released", updateMutedTracks],
  ["encoder_6", updateIsFollowActive],
];

events.forEach(([event, callback]) => gpio.on(event, callback));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment