Common Rewrites
This guide collects the small, repeatable rewrites that come up most often when soundscript asks for a more explicit shape.
For the broader style guidance behind these examples, see Idiomatic SoundScript.
Capture Before await
When a narrowed property needs to survive an await, capture the stable value into a local before
the suspension point.
// Before
if (box.value !== null) {
await refresh();
return box.value.length;
}
// After
const boxValue = box.value;
if (boxValue !== null) {
await refresh();
return boxValue.length;
}
Capture Before Helper Calls
If a helper call can invalidate an earlier proof, capture the already-proven value before the call or re-check after it.
// Before
if (childRun.status === 'completed') {
logProgress(childRun);
return childRun.status;
}
// After
const childRunStatus = childRun.status;
if (childRunStatus === 'completed') {
logProgress(childRun);
return childRunStatus;
}
Capture Before Callback Boundaries
Callbacks can run after the earlier narrowing is no longer valid. Capture the stable value before the callback or re-check inside the callback body.
// Before
if (box.value !== null) {
items.forEach(() => {
use(box.value);
});
}
// After
const boxValue = box.value;
if (boxValue !== null) {
items.forEach(() => {
use(boxValue);
});
}
Make Mutable Edges Readonly
When a widening fails because the target can write through a mutable edge, make that edge readonly instead of promising a writable surface you do not actually own.
// Before
const animals: Animal[] = dogs;
// After
const animals: readonly Animal[] = dogs;
// Before
interface Kennel {
animals: Animal[];
}
// After
interface Kennel {
readonly animals: Animal[];
}
If the broader writable type is still useful, copy into a fresh object or array before widening.
Try(...) Return Shapes
Try(...) works best when the enclosing function already returns the right Result shape.
// Sync Result
function readName(): Result<string, Error> {
const user = Try(loadUser());
return ok(user.name);
}
// Async Promise<Result<...>>
async function readName(): Promise<Result<string, Error>> {
const user = Try(await loadUser());
return ok(user.name);
}
// Object-literal methods
const service = {
readName(): Result<string, Error> {
const user = Try(loadUser());
return ok(user.name);
},
};
If the surrounding shape is not already Result<...> or Promise<Result<...>>, switch to an
explicit isErr(...) branch instead of forcing Try(...) into a non-matching return contract.