View accompanying code on Github
While I was building out my portfolio, I discovered a funny quirk with Typescript generics.
For the uninitiated, a generic is simply a component (function, class, variable,
etc) that spans types
. You'll commonly see them in polymorphic functions. The
contrived example we'll be using is pulled directly from the TS docs:
function identity<Type>(arg: Type): Type { return arg; }
I've always read this from left-to-right, which lead me to believe that Type
needs to be declared if arg
is going to be casted correctly.
In my mind, there were 2 exceptions, which it turns out, are only partially true. The exceptions were:
extends
keywordfunction identityElement<Type extends HTMLElement>(arg: Type): Type { return arg }
=
operator:function identityElement<Type = HTMLDivElement>(arg: Type): Type { return arg; }
So there I was click-clacking along, when I absent-mindedly forgot to include an
argument to the generic and yet, the correct type was returned. This wild
phenomenon is known as type argument interference
. Let me explain:
What's happening is TS is correctly inferring the type of the functional
argument, which it assigns to Type
. So in reality, the following return the
same types
var a = identity<string>(""); var b = identity("");
Even cooler is that it respects the type differences between var
, let
and
const
. As you know, the const
declarator means a variable immutable and
therefore, its type is typically its value:
/** * This is of type `string` since it's mutable. * TS correctly infers it's a string and therefor any mutations will not change its type. */ var a = "hello"; /** * This, however, is of type `"goodbye` since it's immutable. */ const b = "goodbye";
This same inference is applied to generics:
/** The following are all type `"hello"` since they're immutable */ const a = identity("hello"); const hello = "hello!"; const results5 = identity(hello); /** While these are all type `string` since they're mutable */ var goodby = "goodbye!"; const b = identity(goodby); const goodbyeForGood = 'GOODBYE!' var c = identity(goodbyeForGood )
If you want to see this in action, you can checkout the source code to the site you're on right now:
Comments