Eric Haynes
Eric Haynes

Reputation: 5786

Flow types - map Object of functions to function results

I'm trying to type a function with flow which, given an Object type, accepts an object where each property is replaced by a "create" function creating the value. I expected to be able to map the value types using $ElementType typed to the $Keys, but it doesn't appear to be properly associating keys and values.

Here's a simplified example:

// @flow

type TestType = {
  foo: number,
  bar: string,
}

declare function create<
  K: $Keys<TestType>,
  V: $ElementType<TestType, K>,
  O: {[K]: () => V}
>(obj: O): TestType

const tmp = create({
  foo: () => 5,
  bar: () => 'whatever',
})

But flow reports that each type is incompatible with the value of the opposite key. E.g. foo's value is incompatible with bar's:

Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ tmp/syntax/flowTest.js:15:14

Cannot call create with object literal bound to obj because number [1] is
incompatible with string [2] in the return value of property foo.

 [2]  5│   bar: string,
       :
     11│   O: {[K]: () => V}
     12│ >(obj: O): TestType
     13│
     14│ const tmp = create({
 [1] 15│   foo: () => 5,
     16│   bar: () => 'whatever',
     17│ })
     18│
     19│ // type TodoValues = {
     20│ //   todos: Array<string>,


Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ tmp/syntax/flowTest.js:16:14

Cannot return 'whatever' because string [1] is incompatible with number [2].

 [2]  4│   foo: number,
       :
     13│
     14│ const tmp = create({
     15│   foo: () => 5,
 [1] 16│   bar: () => 'whatever',
     17│ })
     18│
     19│ // type TodoValues = {

Live example: Try Flow REPL

Upvotes: 2

Views: 846

Answers (2)

user11307804
user11307804

Reputation: 828

I think the Flow $ObjMap example is pretty close to what you want. It basically works out of the box (renaming run to create):

// let's write a function type that takes a `() => V` and returns a `V` (its return type)
type ExtractReturnType = <V>(() => V) => V;

declare function create<O: {[key: string]: Function}>(o: O): $ObjMap<O, ExtractReturnType>;

const o = {
  foo: () => 0,
  bar: () => 'foo',
  baz: () => true,
};

type TestType = {
  foo: number,
  bar: string,
  baz: number, // Error since true is not a number
}

const p: TestType = create(o);

Try Flow

Upvotes: 2

Marko Savic
Marko Savic

Reputation: 2384

Just one of those two properties foo / bar can be passed to obj param of function create. You can't put them together because you have UnionType.

K: $Keys<TestType>, // UNION: number | string
V: $ElementType<TestType, K>, // UNION: foo | bar
O: {[K]: () => V} // UNION: foo: () => number | bar: () => string

This works:

type TestType = {
  foo: number,
  bar: string,
}

declare function create<
  K: $Keys<TestType>,
  V: $ElementType<TestType, K>,
  O: {[K]: () => V}
>(obj: O): TestType

const foo = create({ foo: () => 5 })
const bar = create({ bar: () => 'whatever' })

Upvotes: 1

Related Questions