A transform helper has two type relationships: the element the callback receives and the element it returns. mapValues<T, U>(items: readonly T[], transform: (item:T) => U): U[] says each output is the callback’s U, not necessarily the input T.
The caller usually supplies no explicit type arguments because array and callback context provide them. A callback can only assume the fields guaranteed by its input contract. A callback promised to handle every Book cannot safely demand an ExtendedBook containing extra required fields.
Under strictFunctionTypes, ordinary function-typed callbacks are checked against these parameter requirements. There are special compatibility rules for method declarations; prefer a function property or function parameter when expressing a callback contract. We do not need to explore variance terminology to use the safe rule: the callback must handle every input the helper promises to pass.
Keep one generic signature here: T names the input element type and U names the output element type. Their names describe a relationship; neither exists as a runtime value.
Predict, run, and explain
Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.
function mapValues<T, U>(items: readonly T[], transform: (item: T) => U): U[] {
return items.map(transform);
}
const lengths = mapValues(["Types", "API"], text => text.length);
console.log(lengths.join(","));Enable JavaScript to edit and type-check this example.
Check your prediction
5,3 Read the compiler error
The callback requires more than the caller promises to supply. ExtendedBook below explicitly names both required fields; the plain Book passed by apply has no subtitle.
type Book = { title: string };
type ExtendedBook = {
title: string;
subtitle: string;
};
function apply(book: Book, render: (book: Book) => string): string {
return render(book);
}
apply({ title: "Types" }, (book: ExtendedBook) => book.subtitle);Enable JavaScript to edit and type-check this example.
Check your diagnosis
Accept Book and handle only its guaranteed fields. If subtitle is required by the operation, change the whole input contract instead of narrowing only the callback parameter.
Infer output from what the callback returns
A reusable transformation has separate input and output element types. T describes what the helper supplies; U describes what the callback produces. Keeping both parameters preserves useful information without forcing every transformation to return its input type.
The callback must accept every input allowed by the helper contract. A function that requires an extra property is unsafe even if one current fixture happens to carry it. Check this relationship at the public boundary rather than relying on an accidental richer record.
function mapValues<T, U>(
values: readonly T[],
transform: (value: T) => U
): U[] {
return values.map(transform);
}
const lengths = mapValues(["Types", "API"], value => value.length);
console.log(lengths.join(","));
console.log(lengths.every(value => value >= 0));Enable JavaScript to edit and type-check this example.
Check your prediction
5,3
true U is number because the callback returns length. A later push of text should fail checking even though the input happened to be text.
type Book = { title: string };
function labels(
values: readonly Book[],
render: (book: Book) => string
): string[] {
return values.map(render);
}
console.log(
labels([{ title: " Types " }], book => book.title.trim()).join(",")
);
console.log(labels([], book => book.title).length);Enable JavaScript to edit and type-check this example.
Check your prediction
Types
0 The renderer handles exactly the promised fields. An empty list makes no callback invocations and returns an empty output, which is another part of the transformation contract.
Try the rule on new inputs
Use one input list to produce numbers, booleans and new records in separate calls. Inspect the return types. Then make a callback demand an extra required field and read the parameter-compatibility error.
Your turn
Generic Callbacks: practise
Write mapValues and transform books into numeric page counts. Print 10,20. The returned list must be number[], so pushing text should be a compiler error.
// Use separate type parameters for inputs and outputs.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The callback determines the result element type.
Hint 2: a first step
Declare transform as (item: T) => U.
Show one solution
function mapValues<T, U>(items: readonly T[], transform: (item: T) => U): U[] {
return items.map(transform);
}
const pages = mapValues(
[
{ title: "A", pages: 10 },
{ title: "B", pages: 20 }
],
book => book.pages
);
console.log(pages.join(","));Enable JavaScript to edit and type-check this example.
10,20Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?
Change it and test it
Try a callback requiring an extra required field not present on every input. Explain why this is unsafe even if one demonstration record has that field. Then compare the direct array.map call with the helper: use abstraction only if it adds useful domain behavior.
Completion is saved in this browser only. It does not measure mastery.