Skip to content

String literal types and union type guards toward tagged unions #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Nevor opened this issue Nov 27, 2014 · 4 comments
Closed

String literal types and union type guards toward tagged unions #135

Nevor opened this issue Nov 27, 2014 · 4 comments

Comments

@Nevor
Copy link

Nevor commented Nov 27, 2014

As OCaml programmers, you know that tagged unions are very useful a wide range of situation and it might be interesting to encode them in Javascript in a safe way (i.e. type checked by Flow).

Current workaround and motivating example

One way of encoding them is to create a union type of disjoint types, the usual way is to use a tag property that will defer for each "constructor". You already encode them that way in your flux-chat example :

Definitions :

type ServerReceiveRawMessagesAction = {
  type: any;
  rawMessages: Array<RawMessage>;
};

type ServerReceiveRawCreatedMessageAction = {
  type: any;
  rawMessage: RawMessage;
};

type ServerAction = ServerReceiveRawMessagesAction
                  | ServerReceiveRawCreatedMessageAction;

[...]

Destruction :

MessageStore.dispatchToken = ChatAppDispatcher.register(function(payload: any) {
  var action = payload.action;

  switch(action.type) {
[...]

    case ActionTypes.RECEIVE_RAW_MESSAGES:
      _addMessages(action.rawMessages);
      [...]
      break;

    default:
      // do nothing
  }

});

Unfortunately, in these example, you are using the any type for tag and payload. The construction and destruction is not safe, anything is usable as a tag and anything can go through a switch case.

// bad construction

var invalidButAccepted : ServerReceiveRawMessagesAction = {
  type : "foobar",
  rawMessages : []
};

// or if we pass a bad type to the switch case

var action = { type : ActionTypes.RECEIVE_RAW_MESSAGES; foobar : 3 };
// will break at _addMessages(action.rawMessages);

A quick safe solution is to use some kind of singleton type as a tag (for instance a string literal type) and to extend type guards to the switch statement and to narrow union types.

Previous proposal in Typescript

This very situation is also a problem we spotted in Typescript too. Leveraging the recent addition of type aliases and union type, we have made a same proposal to Typescript developers. microsoft/TypeScript#1003. Furthermore, we have developed a prototype Typescript extended to handle string literal types in implementation files and extended if and swtich guards.

In our implementation, your chat example would roughly become :

type ServerReceiveRawMessagesAction = {
  type: "RawMessages";
  rawMessages: Array<RawMessage>;
};

type ServerReceiveRawCreatedMessageAction = {
  type: "RawCreatedMessage";
  rawMessage: RawMessage;
};

type ServerAction = ServerReceiveRawMessagesAction
                  | ServerReceiveRawCreatedMessageAction;

[...]
MessageStore.dispatchToken = ChatAppDispatcher.register(function(payload: Payload<...>) {
  var action = payload.action; // action of right type

  switch(action.type) { // valid because type is common to all types in union
[...]

    case ActionTypes.RECEIVE_RAW_MESSAGES: // assuming this of type "RawMessages"
      _addMessages(action.rawMessages); // action is narrowed to the right type and checked okay
      [...]
      break;

    // case "foo" : would be an error 

    default:
      // we could check that the switch is exhaustive
      // do nothing
  }

});

The construction and destruction of the tagged union are then type checked for a safer usage.

Any plan in Flow

The string literal types (albeit not documented) and simple type guards are already available and working as expected in Flow. The question is whether you are planning to go toward this direction in term extended type guards or safe tagged union (in any chosen encoding).

As a side note, we are willing to contribute if it's acceptable but not planned.

@avikchaudhuri
Copy link
Contributor

I gave a talk yesterday pointing this very example, among others, as future work. :)

@Raynos
Copy link
Contributor

Raynos commented Nov 28, 2014

@avikchaudhuri do you have a link to said talk ?

@samwgoldman
Copy link
Member

Is this any different from #20? If not, can we close this in favor of that issue to keep the conversation in one place?

@samwgoldman
Copy link
Member

Merging with #20.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants