DEV Community

Discussion on: The types in TypeScript

 
peerreynders profile image
peerreynders

Inferring the return type of a function is not an "accidental byproduct", but actually the result of your arguments. Depending on what you do with those arguments, the type of the return.

the value I see in inferred return types is that you can change the implementation and get the correct "new type" without having to go and change the output type every time you do this.

So again the return type is a consequence of the implementation.

When you "think in/develop with types" the implementation is a consequence of the desired type - i.e. the relationship it flipped.

Consider this scenario

type MyType = {
  count: number;
  average: number;
};

function fn(values: number[]) {
  const count = values.length;
  const average =
    count > 0 ? values.reduce((sum, value) => sum + value, 0) / count : NaN;

  return {
    count,
    average,
  };
}
Enter fullscreen mode Exit fullscreen mode

We find that MyType also needs total.

type MyType = {
  count: number;
  average: number;
  total: number;
};

function fn(values: number[]) {
  const count = values.length;
  const average =
    count > 0 ? values.reduce((sum, value) => sum + value, 0) / count : NaN;

  return {
    count,
    average,
  };
}
Enter fullscreen mode Exit fullscreen mode

No error that the result from fn is no longer sufficient. If we are lucky somewhere the result will be delivered to an explicitly typed binding, alerting us to the problem with the function (or any others that produce a similar result).

With an explicit return type compilation will identify all the functions that no longer produce the correct type.

type MyType = {
  count: number;
  average: number;
  total: number;
};

function fn(values: number[]): MyType {
  const count = values.length;
  const average =
    count > 0 ? values.reduce((sum, value) => sum + value, 0) / count : NaN;

  // Property 'total' is missing in type '{ count: number; average: number; }' but required in type 'MyType'.
  return {
    count,
    average,
  };
}
Enter fullscreen mode Exit fullscreen mode

i.e. the type is changed before the implementation(s).

A capability in terms of value-orientation:

  • What is the shape of the data I need
  • What is that shape of the data I have
  • How do I transform what I have into what I need.

i.e. contracts (constraints) first, implementation last.

Aside Why type-first development matters

It looks like the intent of that code is to take a number argument and return "100" or 1, so yes, that the correct type for its output.

My intent was to simulate a defect (which perhaps would only be detected at an explicitly typed site of use), i.e. the intended return type should either have been string or number - not string | number.

This way you're effectively doing "JS with types" instead of writing a plethora of types just to write "more TS".

JS Doc TS with (hand written) declaration files is "Typed JS". There's a clean separation between "type space" (TypeScript types) and "value space" (the JS implementation).

The entire point of the index.d.ts and internal.d.ts files is to explicitly formulate a constrained set of types that are expected to be encountered (without having to formulate them "ad hoc").

The problem is that using JS Doc TS well (as far as I can tell) requires a higher level of TS competence (especially its "typing language") than writing TS destined for transpilation.

and many other utils to infer types from static values.

Those features can help to cut down on verbosity and emerge as TS tries to tease whatever type information it can out of the JS value space to make it available in type space. But in terms of design, types are typically formulated before the implementations and defined in type space (rather than extracted from value space).

Type space derived from value space is like drafting a blueprint after the house has already been built.