How can I create a function with a generic argument that can return any type, and have the return type inferred from its usage?
I attempted the following code:
type Thing<T> = <U>(value: T) => U
const shouldMakeStrings: Thing<string> = (a: string) => a + 'foo'
const shouldMakeObjects: Thing<string> = (a: string) => ({ foo: a })
I expected the types of shouldMakeStrings
to be (a: string) => string
and shouldMakeObjects
to be
(a: string) => { foo: string }
However, I encountered an error stating that
'U' could be instantiated with an arbitrary type which could be unrelated to 'string'
Is there a way to define the Thing
type without specifying U, but still allow for any return type?
Below is a more comprehensive example demonstrating what I'm trying to achieve (playground link):
type Thing<T> = <U>(value: T) => U
// Example, this will be a big map of custom types and string representations
type Types = {
'string': string,
'number': number
}
type CallbackMap = {
[P in keyof Types]?: Thing<Types[P]>
}
const callbacks: CallbackMap = {}
const addCallback = <V>(type: keyof Types, callback: Thing<V>) => {
callbacks[type] = callback
}
addCallback('string', (a: string) => a + 'foo')
addCallback('number', (a: number) => { foo: a })
callbacks.string // = callback that takes a string and returns something, with it's type inferred as string from `addCallback('string')` above
callbacks.number // = callback that takes a number and returns something, with it's type inferred as '{ foo: number }' from `addCallback('number')` above
Here's a more verbose example, although it has other TypeScript-related issues (playground):
type A = { foo: string }
type B = { bar: string }
type C = number[]
type Modifiable = {
'typeA': A,
'typeB': B,
'typeC': C
}
type Modifier<T> = <U>(value: T) => U
type ModifierMap = {
[P in keyof Modifiable]?: Modifier<Modifiable[P]>
}
const modifiers: ModifierMap = {
'typeA': (a: A) => a
}
const setModifier = <V extends keyof Modifiable = keyof Modifiable>(type: V, callback: Modifier<Modifiable[V]>) => {
modifiers[type] = callback
}
setModifier('typeA', (input: A) => input.foo + 'a')
setModifier('typeA', a => a.foo)
// With the aim to be able to do something like this:
const makeModifiedA = (modifiers?: ModifierMap) => {
const a = { foo: "hello world" }
return modifiers?.typeA ? modifiers.typeA(a) : a
}
makeModifiedA(modifiers) // = "hello world"
setModifier('typeA', (input: A) => ({ ...input, upperFoo: input.foo.toUpperCase() }))
makeModifiedA(modifiers) // = { foo: "hello world", upperFoo: "HELLO WORLD" }