一键导入
currying-inference
Use when generic types aren't inferred. Use when builder patterns need better types. Use when creating new inference sites.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when generic types aren't inferred. Use when builder patterns need better types. Use when creating new inference sites.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when defining global types. Use when augmenting window. Use when typing environment variables. Use when working with build-time constants. Use when configuring type definitions.
Use when migrating JavaScript to TypeScript. Use when gradually adopting TypeScript. Use when working with mixed codebases. Use when converting large projects. Use when teams are learning TypeScript.
Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations.
Use when creating types from example data. Use when types don't match all cases. Use when API responses vary.
Use when writing type annotations on variables. Use when TypeScript can infer the type. Use when code feels cluttered with types.
Use when defining array-like types. Use when tempted to use number as index type. Use when understanding array keys.
| name | currying-inference |
| description | Use when generic types aren't inferred. Use when builder patterns need better types. Use when creating new inference sites. |
When TypeScript can't infer generic types, create new inference opportunities.
TypeScript infers generic type parameters at specific "inference sites." When it doesn't have enough information at one site, you can create additional sites using classes, currying, or helper functions.
Create inference sites where TypeScript needs type information.
Classes and curried functions provide natural inference points.
Remember:
declare function fetchData<T>(url: string, options: RequestOptions): Promise<T>;
// TypeScript can't infer T
const data = await fetchData('/api/users', { method: 'GET' });
// ^? unknown
// Must specify explicitly
const data = await fetchData<User[]>('/api/users', { method: 'GET' });
The type parameter T has no inference site.
declare function fetchData<T>(
url: string,
parser: (raw: unknown) => T
): Promise<T>;
const data = await fetchData('/api/users', (raw) => raw as User[]);
// ^? User[]
The parser function provides an inference site for T.
// Single function: T not inferred
function makeRequest<T>(url: string): Promise<T>;
// Curried: inference at each call
function makeRequest<T>() {
return (url: string): Promise<T> => {
return fetch(url).then(r => r.json());
};
}
// Usage creates inference site
const getUsers = makeRequest<User[]>();
const users = await getUsers('/api/users');
class RequestBuilder<T = unknown> {
private url: string = '';
setUrl(url: string): this {
this.url = url;
return this;
}
// New method creates new inference site
withParser<U>(parser: (data: unknown) => U): RequestBuilder<U> {
return this as unknown as RequestBuilder<U>;
}
async execute(): Promise<T> {
const response = await fetch(this.url);
return response.json();
}
}
// Type inferred from parser
const users = await new RequestBuilder()
.setUrl('/api/users')
.withParser((data): User[] => data as User[])
.execute();
// ^? User[]
// Without inference sites
class EventEmitter {
on<T>(event: string, handler: (data: T) => void): void;
emit<T>(event: string, data: T): void;
}
// TypeScript can't connect the T's
emitter.on('user', (data) => {
// ^? unknown
});
// With type map
interface EventMap {
user: User;
message: Message;
}
class TypedEventEmitter<Events extends Record<string, any>> {
on<K extends keyof Events>(
event: K,
handler: (data: Events[K]) => void
): void;
emit<K extends keyof Events>(
event: K,
data: Events[K]
): void;
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on('user', (data) => {
// ^? User
});
// Factory provides inference site
function createStore<T>(initial: T) {
let state = initial;
return {
get: () => state,
set: (newState: T) => { state = newState; }
};
}
const userStore = createStore({ name: 'Alice', age: 30 });
// T inferred from initial value
userStore.set({ name: 'Bob', age: 25 }); // OK
userStore.set({ name: 'Charlie' }); // Error: missing age
class QueryBuilder<T = unknown, Selected = T> {
select<K extends keyof T>(...keys: K[]): QueryBuilder<T, Pick<T, K>> {
return this as any;
}
where(predicate: (item: T) => boolean): QueryBuilder<T, Selected> {
return this as any;
}
execute(): Selected[] {
// ...
}
}
interface User { id: number; name: string; email: string; age: number; }
const results = new QueryBuilder<User>()
.select('name', 'email')
.where(u => u.age > 18)
.execute();
// ^? { name: string; email: string; }[]
// Without constraint
function pluck<T, K>(items: T[], key: K): T[K][];
// K not constrained, inference poor
// With constraint
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
const names = pluck(users, 'name');
// ^? string[]
The constraint K extends keyof T helps TypeScript infer K from T.
// Bad: T only in return position (no inference)
function parseJson<T>(): T {
return JSON.parse(data);
}
// Good: T has inference site
function parseJson<T>(parser: (raw: unknown) => T): T {
return parser(JSON.parse(data));
}
// Good: Factory pattern
function createParser<T>() {
return {
parse: (data: string): T => JSON.parse(data)
};
}
const userParser = createParser<User>();
Pressure: "I'll cast with as T"
Response: Assertions bypass type checking. Better to design for inference.
Action: Add inference sites through parameters or currying.
Pressure: "Users can just write fn<Type>(...)"
Response: Inferred types are less work and less error-prone.
Action: Design APIs where inference works automatically.
unknown or any appearing where specific types are expected| Excuse | Reality |
|---|---|
| "Users can specify the type" | Good API design infers types |
| "It's too complex" | Currying and factories are straightforward |
| "Type assertions work" | They bypass safety; inference is better |
// BAD: No inference site for T
function fetch<T>(url: string): Promise<T>;
// GOOD: Parser provides inference site
function fetch<T>(url: string, parse: (raw: unknown) => T): Promise<T>;
// GOOD: Currying
function fetch<T>() {
return (url: string): Promise<T> => ...;
}
// GOOD: Factory
function createFetcher<T>(parse: (raw: unknown) => T) {
return (url: string): Promise<T> => ...;
}
Design APIs that give TypeScript inference opportunities.
When generic types can't be inferred, add inference sites: parameters that use the type, curried functions, or class methods. Good API design makes explicit type parameters unnecessary.
Based on "Effective TypeScript" by Dan Vanderkam, Item 28: Use Classes and Currying to Create New Inference Sites.