Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I have to specify parameter names in a TS function type?

I am using typescript to write some function and this is the one that TS accepts:

export const useSomething = <T>() => {
    const useStorage = <T>(key: string, initialData: T) : [T, (newData: T) => Promise<void>] => {
        const [data, setState] = useState<T>(initialData);
        const setData = async(newData: T) : Promise<void> => {
            await storage.setItem<T>(key, newData);
        };
        return [data, setData];
    }
};

But inithially I wanted to write the return type of useStorage in this way:

[T, (T) => Promise<void>]

Why TypeScript wants me to write down the newData name before the T occurs?

like image 821
Bartłomiej Sobieszek Avatar asked Feb 07 '20 13:02

Bartłomiej Sobieszek


People also ask

Why are optional parameters added in TypeScript?

Developers can use the optional parameter to declare parameters in function optional so that the requirement to pass the value to optional parameters gets eliminated.

Why are optional parameters added?

Optional Parameters are parameters that can be specified, but are not required. This allows for functions that are more customizable, without requiring parameters that many users will not need.

Do parameter names and argument names have to be the same?

The caller's arguments passed to the function's parameters do not have to have the same names.


2 Answers

I think the closest thing you can hope for to a canonical answer is in the GitHub issues microsoft/TypeScript#13152 and microsoft/TypeScript#3081. The gist of the situation is this:

Supporting parameter names in function types is useful as documentation for functions and methods in libraries. A function type (username: string, password: string) => void is the same type as (arg0: string, arg1: string) => void, but the former probably helps developers write code more than the latter does. So support for type-annotated parameter names in function types is intended, documented in the (increasingly outdated) TypeScript spec, and used in libraries all over the place. In the words of one of the language maintainers:

I ... believe that it helps with documentation. Having used Haskell in the capacity that I have, I will say that omitting parameter names ... doesn't help anybody when they're learning a new library. While there's something to be said about an easier syntax, types being the only form of documentation for people often makes it difficult to understand intent ...


Furthermore, where type annotations on named values are supported in TypeScript, the types can be omitted and they will be inferred by the compiler. Failure to infer anything useful results in inferences of any. For example, in the following function:

function f(x, y): void { }
type F = typeof f;
// type F = (x: any, y: any) => void

the type of x and y is inferred as any (and you get a nice error with the --noImplicitAny compiler option). It's the same as the following annotated version:

function g(x: any, y: any): void { }
type G = typeof g;
// type G = (x: any, y: any) => void

Applying the same rule to the type signatures of f and g themselves leads to the following behavior:

type Fprime = (x, y) => void; // --noImplicitAny yells at you here
// type Fprime = (x: any, y: any) => void

type Gprime = (x: any, y: any) => void;
// type Gprime = (x: any, y: any) => void

So when you write (x, y) => void, the compiler interprets x and y as names and not as types. Since the type can be omitted, parameter names cannot. I don't think anyone likes this, but it's been this way for so long that it's apparently used in libraries, so changing this would break real-world code. From the same comment quoted above:

I think it is too late to make this sort of change. Because of the current behavior, interpreting a single identifier as a type would be a breaking change.


So that's the sad answer to this question. Maybe if they could go back in time they would make it so that the parameter name is optional and the type is required, to align more closely with type theory notation, but for now this is what we're stuck with.

Hope that helps; good luck!

Playground link to code

like image 189
jcalz Avatar answered Oct 27 '22 21:10

jcalz


Following type alias can be used to avoid explicit parameter names:

    type F<A extends unknown[], R> = (...args: A) => R
    
    const example1: F<[number, boolean], number> = (x, y) => y ? x : x + 1;
    const example2: F<[number], string> = String;

Also if you mostly deal with 1 argument functions, you may use following type (it is a bit shorter) :

    type F1<A, R> = F<[A], R>

    // or

    type F1<A, R> = (arg: A) => R
like image 1
dolphin278 Avatar answered Oct 27 '22 19:10

dolphin278