Skip to content
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

Type-specific IDs #3421

Closed
palpatim opened this issue Feb 23, 2017 · 2 comments
Closed

Type-specific IDs #3421

palpatim opened this issue Feb 23, 2017 · 2 comments

Comments

@palpatim
Copy link

Question: I would like to define a typed entity map such that the keys must always be IDs from the wrapped type. The desired usage would be something like:

type Foo = { id: string, val: number };
type Bar = { id: string, val: number };

// Using `$PropertyType` as a syntactical placeholder for what I want--
// this doesn't actually work as desired, (and it's unsupported, so
// it may change or be removed in future versions)
export type Id<T> = $PropertyType<T, 'id'>;

// MyMaps should only be able to store objects of type T, keyed by 
// `id` values of T objects
type MyMap<T> = { [key: Id<T>]: T };

const foo1: Foo = { id: "foo1", val: 1 };
const bar1: Bar = { id: "bar1", val: 2 };

// This would pass type checking:
const fooMap: MyMap<Foo> = { [foo1.id]: foo1 };

// But this would fail type checking:
const badMap: MyMap<Foo> = { [bar1.id]: foo1 };

My use case is that I want to have a normalized object structure that manages relationships via IDs. But since the IDs are all of the same underlying type (e.g., string or number), it would be possible to accidentally code something like the badMap example above. I'm hoping FlowType can help prevent that by enforcing that I only ever reference the correct ID type when I'm programmatically constructing my maps.

I think this may not be possible, but wanted to get a definitive answer.

@asolove
Copy link
Contributor

asolove commented Aug 21, 2017

A common way to solve this is to define "opaque types" that are really just numbers or strings underneath but where the type system enforces their distinctness.

Here's a quick example that separates the two object types into their own namespaces to use opaque ids.

foo.js:

// @flow
export opaque type id = string;
export type t = { id: id, val: number };

export function create(id: string, val: number): t {
  return {id, val};
}

bar.js:

// @flow
export opaque type id = string;
export type t = { id: id, val: number };

export function create(id: string, val: number): t {
  return {id, val};
}

And then the code that uses them:

// @flow
import * as Foo from './Foo';
import * as Bar from './Bar';

let foo: Foo.t = Foo.create("stuff", 10);
let bar: Bar.t = Bar.create("stuff", 20);

let m: Map<Foo.id, Foo.t> = new Map();

m.set(foo.id, foo);
m.get(foo.id); // works!
m.get(bar.id); // type error! expected Foo.id but got Bar.id

There's more information and some examples in the blog post on opaque types

I'm happy to continue talking about this if you have more questions about how to use opaque types. But perhaps that kind of discussion is better suited to StackOverflow or somewhere similar for answering questions. I think we can close this issue since it isn't a bug or feature request in Flow itself.

@palpatim
Copy link
Author

palpatim commented Aug 21, 2017

Thanks, I believe opaque types would do the trick. I see that this was added in 0.51.0, so I'll have to look at upgrading.

In any case, thanks for the insights, and we can close this.

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

No branches or pull requests

2 participants