TypeScript: Generic Type vs. Generic Function Type
I recently dug into Jotai’s codebase to get a better grasp of the “flourish” and “energetic” modern state management. However, instead of any classic React rabbit hole that I expected, what tripped me up first was a slight but subtle distinction of the following two type definitions:
// Generic Type
type GenericHello<Value> = () => Value
// Generic Function Type
type Hello = <Value>() => Value
They look different, and surprisingly they are different! (Yeah, I know, I swear I didn’t make a typo.)
At first glance, both seem like generic function types. But there’s a crucial distinction: the first defines a generic type for a non-generic function, while the second defines a non-generic type alias for a generic function (a bit reversed, yeah). In other words, the second one isn’t even a generic type!
// Generic Function Type
type Hello = <Value>() => Value
const hello: Hello<string> = () => 'hello'
// --> Error: Type 'Read' is not generic.
// But the function implementing the type is generic!
const hello_: Hello = <T>() => {
return null as any as T
}
const str = hello_<string>() // ✅ string
const num = hello_<number>() // ✅ number
Even though we can’t pass type parameters to Hello
itself, sometimes this pattern is still very useful — as I noticed in the Jotai source code:
type Getter = <Value>(atom: Atom<Value>) => Value
type Read<Value> = (get: Getter) => Value
Notice the get
function passed to Read
? The generic function type of Getter
is perfect here, since get
needs to read any atom value. Without this self-contained pattern, we would end up sprinkling as
casts everywhere for such a simple API design!
So next time you’re writing a function type and debating where to put the type parameters — take a second. The choice might be more meaningful than it looks.
Honestly, I hadn’t read the TypeScript docs in a while — mostly because I personally doubt if they are really maintaining it rather that those productive release notes. But to their credit, this slight difference is also mentioned by TypeScript Handbook:
Instead of describing a generic function, we now have a non-generic function signature that is a part of a generic type.