❓ 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.
| Signature | Description |
|---|---|
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.
| Signature | Description |
|---|---|
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.
| Signature | Description |
|---|---|
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 underlyingAsyncWhatis executed only when the predicate evaluates truthy on the event.start = false: the underlyingAsyncWhatis 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
Path-expanding mode (no args)
self()converts aWhat: item → itemsintoWhat: path → paths, expanding a search space.
Context-mapping / nominal mode (string or string[])
self(name)→ extractsctx[name]and passes it as input.self(nameIn, nameOut)→ mapsctx[nameIn]to input and stores result inctx[nameOut].self([name1, name2], nameOut)→ extracts multiple props and stores result inctx[nameOut].
Timeout mode (single number)
self(timeoutMs)promotes theWhatinto anAsyncWhatthat fails if not completed withintimeoutMs.
Argument-binding mode (index, value)
self(i, value)injectsvalueinto thei-th position of the args list.
Retry mode (≥3 numeric args)
self(nAttempts, baseDelay, factor, maxDelay)promotes theWhatinto anAsyncWhatthat retries with exponential backoff.- The produced
AsyncWhathas a.stoppedproperty 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
Post a Comment