dumbmatter
dumbmatter

Reputation: 9673

Annotating functions in Flow that operate on union types containing optional parameters

My apologies for the bad title, I don't know how to better describe this :)

I'm using Flow in an application based on an IndexedDB database with auto-incrementing IDs. So basically, I create some objects (with no id property), write them to the database (at which point they are given an id property by IndexedDB), and then read them back out (any object read from the DB is guaranteed to have a numeric id property).

I have some functions that operate on these objects. Sometimes they only operate on objects with IDs, sometimes they only operate on objects without IDs, and sometimes they operate on both. It's the latter case that is trickiest. Here's one attempt, using two different types for the objects before and after being written to the DB (so, without and with the id property, respectively):

/* @flow */

type BeforeDb = {prop: string};
type AfterDb = BeforeDb & {id: number};

var beforeDb: BeforeDb = {prop: 'hi'};
var afterDb: AfterDb  = {id: 1, prop: 'hi'};

function a(obj: BeforeDb | AfterDb): BeforeDb | AfterDb {
  if (typeof obj.id === 'number') {
    console.log(obj.id * 2);
  }
  return obj;
}

function b(obj: AfterDb) {}

var x = a(afterDb);
b(x);

(demo link)

That produces an error on the last line, because it doesn't know that x is of type AfterDb, and I'm not sure how to convey that information appropriately.

Another idea would be to use bounded polymorphisms, except I don't believe this can create something like my a function above because it can't handle the fact that id is sometimes undefined. Like I want to do something like this:

function a<T: {id?: number}>(obj: T): T {
  if (typeof obj.id === 'number') {
    console.log(obj.id * 2);
  }
  return obj;
}

(demo link)

but that doesn't work. If I assigned a dummy value to id so it was always numeric (like -1 instead of undefined) then this would work, but then I'd have to be very careful about remembering to delete the id before the first write to the DB so the real id could be auto-generated, which would be pretty ugly.

After that, I'm pretty much out of good ideas. The one thing I got to work was to use just one type, like:

type Obj = {id?: number, prop: string};

and then explicitly check if the id property is there or not in every function that uses the id property. But that's annoying, because I have a bunch of functions that are only called with the output of IndexedDB, so I already know id is guaranteed to be there. I just don't know how to tell Flow that.

Any ideas?

Upvotes: 2

Views: 140

Answers (1)

gcanti
gcanti

Reputation: 1462

function a<T: BeforeDb | AfterDb>(obj: T): T {
  if (typeof obj.id === 'number') {
    console.log(obj.id * 2);
  }
  return obj;
}

Upvotes: 5

Related Questions