❓ What Class

❓ What Class — Functional Programming with @fizzwiz/fluent

The What class defines a functional interface that supports both evaluation and definition of functions. It parallels the role of the Each class for iterables, providing powerful methods to compose, restrict, and transform functions.

๐Ÿ”ง Instantiation

You can instantiate a What by converting an existing function or argument-value pair.

SignatureDescription
What.of(...args, value)Returns a What producing value when called with args, undefined otherwise
What.as(f)Converts an existing function f to a What

⧉ Snippet: of()

const f = What.of('answer', 42);
console.log(f('answer')); // -> 42

⧉ Snippet: as()

const g = What.as(x => x * x);
console.log(g(4)); // -> 16

๐ŸŽฏ Function Restriction

What instances can be restricted using predicates.

Predicates can accept multiple arguments:

  • value: the individual value from the iterable,
  • ...args: the original input to the function producing the value.
SignatureDescription
if(predicate)Filters the input by predicate(...args)
which(predicate)Filters the output by predicate(value, ...args)

If predicate fails, the result is undefined by default. An error can optionally be thrown if passed as second argument in both methods.

⧉ Snippet: if()

const divide = What.as(x => 1 / x);
const notZero = arg => arg !== 0;

const f = divide.if(notZero);
const F = divide.if(notZero, "division by zero");

console.log(f(0)); // -> undefined
console.log(F(0)); // -> throws "division by zero"

⧉ Snippet: which()

const inc = What.as(x => x + 1);
const positiveResult = y => y >= 0;

const f = inc.which(positiveResult);
const F = inc.which(positiveResult, "negative result");

console.log(f(-2)); // -> undefined
console.log(F(-2)); // -> throws "negative result"

๐Ÿงฉ Function Composition

Just like Each, a What can be composed via several methods. Composition supports chaining, fallback logic, and parallel execution.

SignatureDescription
sthen(that)Sequential composition: that(f(arg))
else(that, matcher)Fallback: use that(arg) if this(arg) throws a matching error
match(...those)Parallel composition: returns [this(arg), ...those.map(f => f(arg))]
each(that)Cartesian application of that on each of the values returned by this

⧉ Snippet: sthen()

const f = What.as(x => x + 1);
const g = What.as(x => x * 2);
const h = f.sthen(g);
console.log(h(3)); // -> 8

⧉ Snippet: else() with error handling

const divide = What.as(x => 1 / x);
const notZero = arg => arg !== 0;

const f = divide
  .if(notZero, "division by zero")
  .else(x => Infinity, /division by zero/)

console.log(f(0));  // Infinity
console.log(f(1));  // -> 1

⧉ Snippet: match()

const f = What.as(x => x + 1);
const g = x => x * 2;
const h = f.match(g);
console.log(h(3)); // -> [4, 6]

⧉ Snippet: each()

const f = What.as(x => [x, x + 1]);
const g = y => [y, y * 2];

// Dynamic each(): applies g to each element of f(x) and flattens results
const h = f.each(g);

const all = h(2);  // -> [2, 4, 3, 6]

๐Ÿ› ️ Event-driven Transformation via when(predicate, emitter, event, timeout, start)

The when() method always promotes a What into an AsyncWhat. It executes conditionally based on events and a predicate, making it possible to start or stop cyclic/asynchronous workflows.

Behavior:

  • start = true: the underlying AsyncWhat is executed only when the predicate evaluates truthy on the event.
  • start = false: the underlying AsyncWhat is immediately invoked (so it can run and be stopped) and will be stopped when the predicate evaluates truthy.

The returned AsyncWhat proxies the .stopped property of the underlying AsyncWhat, enabling external control.

⧉ Snippet: when() with WebSocket messages

// Assume an open WebSocket connection
const ws = new WebSocket("wss://chat.example.com");

const requestId = "123";

// Step 1: message sender with a unique requestId
const sendMessage = () =>
  ws.send(JSON.stringify({
    user: "Me",
    requestId,
    message: "Hello Alice!"
  }));

// Step 2: Alice's reply handler
const handleReply = What.as(evt => {
  const msg = JSON.parse(evt.data);
  console.log("๐Ÿ’ฌ Got reply:", msg.message);
});

// Step 3: predicate to select Alice’s response by requestId
const isResponse = evt => {
  const msg = JSON.parse(evt.data);
  return msg.user === "Alice" && msg.requestId === requestId;
};

// Step 4: waiter for Alice’s reply, within 5s
const waitForAlice = handleReply
  .when(isResponse, ws, "message", 5000)  // implicitely lift sync What into AsyncWhat
  .else(() => console.log("⏰ Alice didn’t reply in time"), "TimeoutError")  // async chain
  .else(() => console.log("⚠️ Connection error"));

// Step 5: compose and execute request–response workflow
await AsyncWhat.as(sendMessage)       // lift sync function into AsyncWhat
  .sthen(waitForAlice)();             // async chain

// Outcome depends on Alice:
// - If she replies with: { user: "Alice", requestId: "123", message: "Hi!" }
//     → logs "๐Ÿ’ฌ Got reply: Hi!"
// - Otherwise → logs "⏰ Alice didn’t reply in time"
// - Or if WebSocket errors occur → logs "⚠️ Connection error"

⧉ Snippet: cyclic execution with start/stop triggers

const cyclic = doSomething.self(Infinity, 100, 1);

const bounded = cyclic
  .when(isStart, emitter, event, undefined, true)  // start execution
  .when(isStop, emitter, event, undefined, false); // stop execution

bounded();

๐Ÿ› ️ Transformation via self()

The self() method transforms a What into another What or promotes it into an AsyncWhat, depending on the arguments provided.

Overload Modes

  1. Path-expanding mode (no args)

    • self() converts a What: item → items into What: path → paths, expanding a search space.
  2. Context-mapping / nominal mode (string or string[])

    • self(name) → extracts ctx[name] and passes it as input.
    • self(nameIn, nameOut) → maps ctx[nameIn] to input and stores result in ctx[nameOut].
    • self([name1, name2], nameOut) → extracts multiple props and stores result in ctx[nameOut].
  3. Timeout mode (single number)

    • self(timeoutMs) promotes the What into an AsyncWhat that fails if not completed within timeoutMs.
  4. Argument-binding mode (index, value)

    • self(i, value) injects value into the i-th position of the args list.
  5. Retry mode (≥3 numeric args)

    • self(nAttempts, baseDelay, factor, maxDelay) promotes the What into an AsyncWhat that retries with exponential backoff.
    • The produced AsyncWhat has a .stopped property to cancel retries.

⧉ Snippet: self() (Paths)

const f = What.as(x => [x + 1, x + 2]);
const h = f.self();
const paths = h(Path.of(1));
console.log([...paths]); // -> [[1,2], [1,3]]

⧉ Snippet: self() (Partial Application)

const sum = (a, b, c) => a + b + c;
const partial = What.as(sum).self(1, 10);
console.log(partial(2, 3)); // -> 15

⧉ Snippet: self() (Nominal)

const f = What.as(x => x * 2).self(['a'], 'b');
console.log(f({ a: 5 })); // -> { a: 5, b: 10 }

⧉ Snippet: self() (Timeout)

const longTask = What.as(() => { while(true){} });
const bounded = longTask.self(1000); // fails if >1s
await bounded();

⧉ Snippet: self() (Retry)

let attempt = 0;
const flaky = What.as(() => {
  if (++attempt < 3) throw new Error("fail");
  return "ok";
});

const resilient = flaky.self(5, 100, 2, 1000);
console.log(await resilient()); // -> "ok" after retries

Comments

Popular posts from this blog

๐Ÿงฑ v0.0.0-dev.4

๐Ÿ‘… v0.0.0-dev.12 — Streamlining the Library

๐Ÿง  The Power of the when() Method