What's with all the one-letter types?

I'm enjoying typescript but having an issue understanding generics in some of d.ts files I'm using. I've noticed many use one letter to represent a type. When I write types, I use full one-or-two-word Pascal case as names and it works just fine. I was trying to interpret redux-thunk so redux's dispatch would play well, but the types are unclear to me. I feel like the concept of appropriate variable naming would apply here.

I imagine there's a reason to the madness. But what is it?

Posted Jan 11, 2019, 9:18 PM UTC

1134
cspotcode•

I think this is just a legacy computer science convention. Typically generic types are T, U, V... unless you have a better name for them. But even then, it's usually one letter.

In my code I often use longer generic names especially while I'm prototyping. Once I figure out the design, I might shorten them back to one letter for readability.

19
drofzz•

TSource and TResult are also common :)

11
backs_pace•

So much more readable

3
BithTreeOP•

When you say "for readability", do you mean readability for you or for other people? Do companies that use typescript and this style have a list to give while onboarding or are devs supposed to memorize them? It seems like so much cognitive overhead.

4
cspotcode•

It depends entirely on the situation; there's no cut-and-dry answer. "I might shorten them back"

For example, if I simplified the type so that it only has a single generic parameter, and its role is obvious, then I might simplify it to T. T is immediately recognizable as the first generic parameter, since that gets ingrained over time during a computer science degree and job experience writing various languages.

If there are multiple generic parameters and their roles aren't clear, I would probably stick to longer names.

And, where appropriate, I clarify within JSDoc.

To me, "for readability" always means "for other people" or "for my team specifically." I think that's software development common sense.

9
BithTreeOP•

I understand the one-letter versions in some cases, such as P (props) and S (state) for React. It makes perfect sense because they are core concepts. But my "prettier" type defs are definitely not pretty because of the long type names, a disadvantage for sure.

Edit: I like the doc solution. That'll help me write them cleaner, at the least.

1
unflores•

If it's not a generic, that just breaks my heart.

2
cr125rider•

Correct for sure in making longer names. More descriptive is good. If something is TRUELY generic for any type T makes sense. Past that, be descriptive!

2
Drawman101•

Generics can be difficult to read if you aren’t used to them, but the bright side is usually they are used in small enough snippets of code that you can work through it

11
icholy•

It’s a convention like i in for loops. You get used to it after a while.

8
gamebuster•

Generics are commonly one-letter in languages like Java & C++. (at least in my experience) Why? I dunno... maybe it's to emphasise it's a generic type, not an interface, abstract or concrete type.

I saw some typescript code that used PascalCase type names and only then I realised one-letter types aren't a requirement. However, it took me a while to realise and it was really something to get used to.

7
gammelini•

When I see T to me this represents that it requires a type.

For example in C# List<Of String> is defined as List<Of T>.

Same with TypeScript with Array<string> is defined as Array<T>

Im on mobile so the syntax may not be completely correct.

4
BithTreeOP•

That is simple and easy to understand. Chained generics like Foo<S, E extends G, A> and Bar<R, F extends Foo> are my challenge. I essentially have to dig through source code to see what the initials may be if they aren't function arguments, obj properties, or there aren't any docs. Edit for bad letters.

9
gammelini•

Oh ok, gotcha. So TypeScript allows you to provide generics but with constraints by providing the F extends Foo. Essentially the generic you provide for F must contain the members defined in Foo. The TypeScript documentation has some simple examples of this such as the one below:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

The section below this example also provides an additional example similar to your Foo<S, E extends G, A). Essentially this is the same as above except E has a constraint.

Example from the TS docs:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

Technically you could write these as a single letter or as descriptive as you want, it comes down to preference and coding style.

I hope that helps.

(Again, mobile)

3
BithTreeOP•

Thanks for taking the time to explain. I'm rather familiar with OOP (started with Java and python) and static typing (did some Java and C). If the type names are clear or initials are obvious, I can read types well. I bet your explanation will help others.

My issue is knowing exactly what each letter represents if it's not stated anywhere. An example that showed up in another comment was me thinking R in a generic was "returns" (it was an async action, so I was thinking Promise<R>), but another suggested "reducer". It's not so bad in a generic by itself but compounds confusion when 5+ different types using 3-4 vague generics are tied together. I can dig through the code to figure it out, but it's time-consuming for each library. Even a simple docstring with "takes reducer R, action A, and enhancer E" would do wonders.

3
gamerfiiend•

Do you maybe mean generics? Like myVariable: T

3
rco8786•

Single letter types are almost always generic. Usually a “T” but lots of letters may get used in a generic heavy section of code.

3
scaleable•

I also agree. Terrible convention.

​

Letters starting a U,V are common on some math areas.

3
ClutchHunter•

Simple generics benefit more from terseness, e.g.:

type UnwrapPromise<T> = T extends Promise<infer U> ? U : never;

Whereas complex generics full of context do benefit a lot more from longer type names. IMO.

3
madcaesar•

How does it benefit? I literally understand nothing in that line :/

1
ClutchHunter•

That'd just be because you don't understand generics and/or conditional types/inferring well enough. It's simpler than it looks; admittedly it took a while for me to wrap my head around it.

We define a generic type called UnwrapPromise that accepts one type argument, we're calling it T because it can technically be anything (we need to define it this way (instead of say specifying what it is upfront with extends) due to the quirk that you have to use conditional types to leverage the awesome infer keyword).

We then check if this type extends a Promise. If it does, we assign the inner type (e.g. the inner type of Promise<string> would be string) to U, and return that following the ternary. If it doesn't, we return never, which essentially means the compiler stopping you in your tracks and forcing you to pass it nothing other than a promise.

Result is that UnwrapPromise<Promise<number>> becomes number!

You could assign longer type names, for example T could be TypeArg and U could be PromiseInnerType or something, but when you understand the syntax well enough it's easier to follow with smaller letters. It's identical to how a traditional for loop is easier to follow with a single character counter variable (e.g. i: for (let i = 0; i < 10; i++) {}).

2
madcaesar•

Hm do you have a piece of example code using this typing? I'm still not sure I understand :(

1
[deleted]•

[removed]

1
hasparus•

type UnwrapPromise<T> = T extends Promise<infer U> ? U : never;

We want to "unwrap promise". We read extends and ternary operator as in runtime code, so if T is a Promise of U, we return U (and we totally don't care what U is), else we return never.

1
recursive•

Could you show an example?

2
BithTreeOP•

A simple example here: Redux thunk index.d.ts. I can infer that S is state and A is action. R might be return? E is weird because it seems to be for then optional configuration, but its 2nd in some of the type defs, so that makes me think it's not.

This is a small example because you can easily dig through it, but when there's 20 or 30 types using 2-4 generic arguments each, they become hard for me to follow. I dont have any problems breaking regular code down.

1
recursive•

I'm not familiar with any standard practice or style guide that would recommend naming things like this. This just looks like bad naming to me. But I'm not too familiar with React stuff.

I'm sure it's scarce consolation, but I find it difficult to read too.

2
Charles_Stover•

I believe R is a reducer function, but it may be used in multiple places as different things.

2
BithTreeOP•

That makes so much more sense. Thanks for the help.

1
smthamazing•

I would personally use longer type names in these more complex cases, even though I use T and U for simpler, more abstract definitions.

2
springy•

It is because functional programming originated in algebra, and algebra uses only single letters for variables, since letters together imply multiplication: "ab" means "a*b".

2
fk2106•

A generic type doesn’t need to be more than one letter because they don’t have a meaning (they are generic), they are just symbols that you can follow through the code. If a template is expected to be of a certain type, you’d usually see things like “T extends MyType” when it is defined for the first time. For example a generic List like List<T>, the T can be virtually anything, so there is no incentive to have it more explanatory. But now say you are making a class that expects a specific kind of object, you’d do MyList<T extends MyModel>.

On the other end, one can argue for more verbose generic types, like GenericType List<GenericType>, MyList<GenericModel extends MyModel>. Which in some case might be more readable. But the doc or the code should be self explanatory enough.

1
SlugFiller•

It's been roughly said below, but I feel this needs to be stated explicitly: They're NOT types. They're parameters.

They're parameters of type "type". Think of them as a variable, which value is some type. Like you could write "T = int" in much the same way you would write "i = 0".

This could be confusing because the "program" in which these are variables is "executed" at compile time. You can't actually assign a value into a generic type. But you can use it in a function-like call. e.g. you have: myFunc<T>(s : T) : void And then you can call: myFunc<int>(0) Here, we first assigned (at compile time), through parameter specification, "T=int", and then assigned (at run time), in the same way, "s=0".

Just as you'd normally have short-named variables and parameters for "regular" types like "i", "j", "n", "x", "y", etc, you also use short names for generic types, as in, variables of type "type".

One might argue that even for regular variables, it's better to have longer names. I'd counter argue with this: for (var indexForRunningOverArray = 0; indexForRunningOverArray < arrayImRunningOver.length; indexForRunningOverArray++) { if (arrayImRunningOver[indexForRunningOverArray] === valueImSearchingFor) { return true; } } return false; Verbose is often the opposite of readable. That's why the word "brevity" exists.

So yeah, these are parameters, their usage is understood from the context where you find them (like with any parameter), and they're short for brevity. As a rule of thumb, long names should be reserved for things you export/use across files/APIs.

1