Flow: Use return type of a function

Created on 23 May 2017  路  22Comments  路  Source: facebook/flow

Flow finds return type of functions very well, can we use that type in other declarations in any way?

Example of what I would like to achieve:

const mapStateToProps =  (state: ReduxState) => ({
  val1: selector1(state),
  val2: selector2(state),
})

type Props = typeof mapStateToProps()

class Home extends Component {
  props: Props;
  render () {
    return <div />
  }
}

const connector = connect(mapStateToProps);

export default connector(Home);

Most helpful comment

There are a couple of hacky ways to do this:

  1. Use "existential" (star) type:
type Return_<R, Fn: () => R> = R;
type Return<T> = Return_<*, T>;

function foo() {
  return 1;
}

const x: Return<typeof foo> = ''; // error
  1. Call the function inside of a Flow comment block:
function foo() {
  return 1;
}

/*::
const __result = foo();
*/

const x: typeof __result = '';

All 22 comments

There are a couple of hacky ways to do this:

  1. Use "existential" (star) type:
type Return_<R, Fn: () => R> = R;
type Return<T> = Return_<*, T>;

function foo() {
  return 1;
}

const x: Return<typeof foo> = ''; // error
  1. Call the function inside of a Flow comment block:
function foo() {
  return 1;
}

/*::
const __result = foo();
*/

const x: typeof __result = '';

@vkurchatkin Awesome example, I am now wondering why does the same trick not work for the function arguments:

type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;

type Arguments_<A, F: (...args: A) => any> = A;
type Arguments<F> = Arguments_<*, F>;

function foo(n: number) {
  return n;
}

const r: Return<typeof foo> = ''; // error
const a: Arguments<typeof foo> = ['']; // no error :(

https://flow.org/try/#0C4TwDgpgBAShwFcBOA7A+gHhgGigMQC4oAKAOnIEMkBzAZyIEEkkKQMKUQA+ASigF4usIf1gBuAFChIseMhQYAKiNmJUmAFS5lkqeGhNqCALYQUwWpga5CJcqSp1GfQVA7cBUBpOkGaJswsMPBVDAPNLDC18Ll0AMwQUAGNgAEsAexQoOPT04hQiFBMAIwgkPgBvCSgoJDlUKBRJAF8JCSTM2mBaojg1BV90uOzclQByMbEoAHppqDKkdKR2zu6KRn9TCIxB4Zz0lQBtCYBdKdnG9PnmJagCYiA

That's because type of arguments flows in the opposite direction. * captures whatever flows in, not what flows out. Simple example:

var x: * = 1; // number ~> *
declare var y: typeof x;
(y: string); // error

declare function fn(x: number): void;

declare var z: *;

fn(z); // * ~> number

declare var u: typeof z;
(u: string); // no error

The same happens when function flows into another function, like in your example.

@vkurchatkin Do you think your Return utility type could be added to the language? Seems like a useful thing

Not Return specifically, but Apply makes sense to me

@nickmessing is this working for u? Do u have a repo example of this?

@sibelius, yes, this helper works fine:

type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;

Add ExtractReturn helper

// https://hackernoon.com/redux-flow-type-getting-the-maximum-benefit-from-the-fewest-key-strokes-5c006c54ec87
// https://github.com/facebook/flow/issues/4002
// eslint-disable-next-line no-unused-vars
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
export type ExtractReturn<F> = _ExtractReturn<*, F>;

Extract return of mapStateToProps and mapDispatchToProps

const mapStateToProps = state => ({
  user: state.user,
});
const mapDispatchToProps = dispatch => ({
  actions: {
    logout: () => dispatch(logout()),
  },
});

type ReduxProps = ExtractReturn<typeof mapStateToProps>;
type ReduxActions = ExtractReturn<typeof mapDispatchToProps>;

Add ReduxProps and ReduxActions to your Component props

type Props = {} & ReduxProps & ReduxState

class EntriaComp extends Component<Props> {}

I think we can use $Call to improve this as well

cc @calebmer

can we close this?

Can ExtractReturn be added to the global Flow helpers as $Return?

you can use $Call instead of ExtractReturn

Proposed solutions do not work for my case: Flow Try

I need to explicitly type returns of actionCreators methods to make these work: Flow Try

The dream is using $ObjMap to completely avoid duplication:

const actionCreators = {
  // ...
};

export type Action = $Values<$ObjMap<typeof actionCreators, ExtractReturnType>>;

Edit: I think my problem is that the inferred return types not being literal. I guess what I need from Flow is return type of a function like () => 'hey' being inferred as 'hey' instead of string.

Hi everyone,

@vkurchatkin, so based on what you wrote on this comment, I assume there is no way to extract function arguments, or is any?

I can't get it to work either. Here's a very simple example.

Does not throw:

type Props = ReduxProps & { user: string };
this.props.user.foo();

Does throw:

type Props = { user: string };
this.props.user.foo();

When using ExtractReturn helper, user type being "string" should be properly propagated from global state all the way to this.props. But it doesn't.

UPDATE

Finally got it to work using spread and exact helper!

type Props = {|
   ...ReduxProps,
  customProp: customType
|}

This probably can be closed.
/cc @mrkev

Hi @kangax,

I've just seen your comment, would you mind to share ReduxProps type?

I'm asking you this as I believe the error you were having was caused by the fact that when intersecting types (using &) if any of the them share a property, the resulting type would hold on that property an union of all the values for it, does it make sense? was your error caused by this?

this is the version using $Call, it is much faster than using *

export type ExtractReturn<Fn> = $Call<<T>((...Iterable<any>) => T) => T, Fn>;

Example of extracting ReduxProps:

type ReduxProps = ExtractReturn<typeof mapStateToProps>;

https://flow.org/try/#0C4TwDgpgBAogHsATgQwMbAEoWAV0QOwB4AxfAPigF4oASAYWQBtHDCAVMgCk4Do+BJYBBQAjRhELJ8IMgEoqFNvMqKANFFJkA3ACgoO0JChYAJjjgBlYMiFUoAbz1RUALigBnJAEt8Ac1U6AL56OgBmOPjoXgD2+FDInJ42EG6m5lbJ8o5QUIjYeHHZOVAibklCPKgBOcHBYRFRsSWcYMgoALbubvBIaJj5BISGENGh8XIOTi1tyJ08pR7efrK6OdMd7vNuItHR4lIrQUA

can we close this?

Sure 馃憤

I'm having some issues with typing mapDispatchToProps as an object of actions:

const mapActionsToProps = {
  logout
}

connect(null, mapActionsToProps)(Comp)

Since it's a simple object of functions, I should be able to use typeof, right?

type ReduxActions = typeof mapActionsToProps

type Props = {} & ReduxActions // or type Props = { ...ReduxActions }

class Comp extends Component<Props> {}

But it doesn't work, Flow loses the inferred type of the actions and see them as any.

I reported the bug (?) here: https://github.com/facebook/flow/issues/6797

Does someone have a workaround? Or am I doing something wrong? (cc @sibelius )

Edit: so it appears only when the actions are imported from another file. 馃く

Hello. I am trying to extract union type of "foo": type Foo = "a" | "b" | "c"
but I could get only type Foo = "string"|"string"|"string"

const getObject = () => {
  const r = {
      a: {foo: "a"},
      b: {foo: "b"},
      c: {foo: "c"}
  }
  return r;
 }

type ResultType = $Call<typeof getObject>;
type Values = $Values<ResultType>
type AUnion = $PropertyType<Values, "foo"> // should be union of literals, but it's union of strings

const a: AUnion = "a"
const b: AUnion = "b"

//should be an error here
const fooBar: AUnion = "bar"

https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAodBjOA7AzgFzAHMBTfAeQCMArEjQgXjAAoBKMBgPjAG9UwwWPIQBOHXvwFSAhgC5eUOHHkAiaSoC+AGklSwleT0XKwKypp16BGQ8dUZNuqcGBhpYeIThQwcfAAsSMQBrEgBPXDAEAEsAhSVVAhFo7CIPWKDpGE0wADcsgFcSSQ1JETICkWwwEQBuflLUfDCABxIwACUSXAKYfAAVVvamABIAYSyYAB5mtu9iMipaek562faANULu8RGtmCLcKa6evsG2ziahsABBAFVsaJxdgAURODaRZvOSKf3DrSmYwqbguMC4fxwXoAE307QKj2e8xgGSqWVwgMoBUIsQA5JEEU9qvMkikiLhJKghAQ3PJ7ojqkw1CoqTgaQZbg8ieIzCzUC4IVCYLDKO1ogBbFpwXDRSgwYrUwjGABC0hEdK5zyZxkoapZQA

Hello, can't figure out how to use $Call<typeof myfunc>, in case myfunc returns promise.
Smth like:

function asd(): Promise<number> {
  return Promise.resolve(3);
}

(async () => {
  const num: $Call<typeof asd> = await asd();
})()

Getting error:

7:   const num: $Call<typeof asd> = await asd();
                                    ^ Cannot assign `await asd()` to `num` because number [1] is incompatible with `Promise` [2].
References:
1: function asd(): Promise<number> {
                           ^ [1]
1: function asd(): Promise<number> {
                   ^ [2]

Promise chain also not working as expected:

function asd(): Promise<number> {
  return Promise.resolve(3);
}

asd().then((num: $Call<typeof asd>) => {});

Getting error:

5: asd().then((num: $Call<typeof asd>) => {});
                    ^ Cannot call `asd().then` because `Promise` [1] is incompatible with number [2] in the first argument.
References:
1: function asd(): Promise<number> {
                   ^ [1]
1: function asd(): Promise<number> {
                           ^ [2]

https://flow.org/try/#0GYVwdgxgLglg9mABAQwM4BMAUBKAXIgBQCc4BbGVAUwB4wRSAjSogPkQG8AoASCMqhBEkxMhUoA6PqjgAbAG6VMAZmwBuTgF9O2zGgCekRDkQBeNl06JEEBKiiI6pfABIAwshkzqUPQAdKcMAoGGwmKADuyDD2aFhqmtg42kA

@alexandrricov

function asd(): Promise<number> {
  return Promise.resolve(3);
}

(async () => {
  const num: $Call<typeof $await, $Call<typeof asd>> = await asd();
})()
Was this page helpful?
0 / 5 - 0 ratings