facebook / flow

Adds static typing to JavaScript to improve developer productivity and code quality.
https://flow.org/
MIT License
22.09k stars 1.86k forks source link

Why is passing multiple function possibilities so hard? #6630

Closed bradennapier closed 4 months ago

bradennapier commented 6 years ago

Why am I always fighting the ability to do this? It seems libdefs make this possible but its impossible to do outside of doing library definitions - it's quite a disconnect.

For example, if I have an "event" parameter which will define what to expect for the remaining arguments... I want them to check the event, then have Flow know the type of data based on their type refinements that are done.

I generally start by trying to use union since it seems like what I would use for this... but that doesn't work... then somewhere someone said to use intersection types when they are functions for the same functionality expected by Union - but thats not working...

With libdefs you simply define the function multiple times. How do I do this?

export type WS$EventHandler = ((event: 'error', data: Error) => any) &
  ((event: 'connect', data?: void) => any) &
  ((event: 'disconnect', reason: string) => any) &
  ((event: 'message', data: WS$Response) => any);

I started with trying to use interface here

interface EventHandler {
  (event: 'error', data: Error): any;
  (event: 'connect', data?: void): any;
  (event: 'disconnect', reason: string): any;
  (event: 'message', data: WS$Response): any;
}

In the end the user should then be able to refine the first argument and flow should know the type of the remaining arguments in this case...

const client = new WSClient(
  'url',
  (event, payload) => {
    // event properly typing to the union of possible events 
    // payload would be union of all possible payloads until refined
    switch (event) {
      case 'message': {
       // WS$Request
        console.log(payload);
        break;
      }
      case 'error': {
       // Error
        console.log(payload);
        break;
      }
      default: {
        break;
      }
    }
  },
);

What I find interesting is that while the event payload is properly giving the union of string literals that I expect, and the payload is the union of all values like I would expect - it is not capable of handling the refinement of the event value to understand what the payload is.

As soon as I attempt to refine event it instantly becomes "string" instead of its literal value so it just seems flow makes refinement impossible.

On top of this, we also create a callback which is impossible to call because it expects that I make all the calls all the time no matter with and I appear to have no way to refine to confirm I am following the protocol.

bradennapier commented 6 years ago

It seems to work on one end but not the other. When defining the callback function it seems to have lost the connection of the event to the payload, however it requires that i properly call the callback function with the appropriate combinations.

M-TGH commented 6 years ago

There is a somewhat okay way to do this, however I must agree it's not really documented anywhere (couldn't find it at least); would end up looking something along the lines of this flow/try.

Declaring the same function multiple times is inferred by flow as if there's multiple branches of a function and what branch you get depends on whether the input matches.

Also looking at your first example that might work better if it were union types instead of intersections. An intersection would imply it does all of it at the same time (e.g. like string & number makes an impossible type implying it's both a number and a string at the same time).

On another note interfaces always seem to miss the mark when I try to use them so... there's that.