Flow: Flow 0.72, '*' as error disables generic type usage with "*" meaning "Use generic type, subtype does not matter"

Created on 10 May 2018  路  22Comments  路  Source: facebook/flow

ORIGINAL TEXT (obsolete, see UPDATE below)

First, I know it's listed in the changelog, but that is not "documentation". What does it mean that the * type, which meant "Flow - you figure this type out" AFAIK is deprecated? What am I to use, any? Isn't this worth an explanation??? I'm pretty sure that internally at FB you didn't just dump this on your developers, or did somebody just declare * as "deprecated" overnight and everybody else just nodded fervently, "of course it is!"?

Flow Blog has the last entry 16 March, so nothing in the blog either. Google writes such a nice blog entry for each new Chrome release with everything relevant for developers.

And why should I not use * anyway? I'm using it mostly as the subtype in some generics, when it does not matter which subtype it actually is. It makes no sense at all for me to write anything else but * in those generic types, for example ObjectCreation<*>, because which subtypes are allowed has already been specified in the type definition of the generic type, and all of them are allowed in those places where I use * instead of a concrete subtype.

Apart from my own generics, to illustrate the problem using well-known generic types: What about arrays or promises, when I only care that it is an array or a promise but not about the contents?


EDIT / UPDATE /NEW TEXT

Generics are a special case — when I only care about the parent type. In that case specifying the allowed subtype is exactly the same as the generics type definition, which is quote a waste of horizontal space. The * allowed me to concentrate on what was important, the parent type. So even when it works I now have to specify the sometimes lengthy (union type!) sub type in generic types even when the subtype does not matter. Having * was much better not just for space, it also captured the intent much better, that the subtype does not matter.

I don't have a single * apart from generic subtypes, so in general I'm okay with this restriction because I already implemented it myself, setting a concrete type everywhere. I just think generics were not considered, or not fully, because the parent type may very well be the most concrete type.

For example, instead of ObjectCreation<*> I now have to write ObjectCreation<OneVersionedObjectTypes | OneUnversionedObjectTypes | OneApplicationTypes> because that is how the type was defined, and in the places that thus far had an * for the subtype only the ObjectCreation parent type itself mattered. So now my line is full horizontally when I just write the type. I cannot use mixed there, I tried, because mixed is not the same as the object union that the generic type was defined with (and it would be wrong, because it isn't anything at all that is possible).

Another example is general generic types such as arrays or promises: When I have functions that only uses the parent type itself and does not care what's inside. For example, when I use a promise for synchronization only but don't care or use the value it resolves with. I may not even know what the subtype will be in such a case, nor does that matter!


TL;DR: Needed is a way to refer to only the parent type of a generic type, disregarding the subtype. Thus far one could just use MyGenericType<*> for that, now the * is deprecated altogether, IMO throwing out the baby with the bathwater.


discussion

Most helpful comment

How would the recompose types work without *? Lots of times the * replaces a much more complex type, especially when chaining HOCs

All 22 comments

Use mixed if you need. Most of the cases may be handled with explicit annotations and implicit inference.

@sibelius Did you read my comment?

@TrySound mixedis a bad replacement. It forces me to do checks because now Flow thinks it could be anything! Also it is not mixed most of the time, it is one of the types allowed for the generic type. I just tried that and it does not work, mixed is NOT the same as the types specified in my generics declarations. mixed has a specific meaning that is incompatible with the actual types I actually use. It cannot be used as replacement for *! Yes I tried just to be sure. It does not work. mixed seems to have, internally, specific type meanings, I guess numberetc., all those standard JS types. I have a subtype for my generics consisting of an object union — and as it looks right now it cannot be represented using mixed.

Then rely on inference. * makes flow slower.

@TrySound Then how do I get rid of the errors? There is nothing in the changelog even. Hence my issue.

I think flow needs something like Typescript infer

Comment moved as EDIT to my initial comment.

I don't want it to sound sarcastic but, if * will be removed which means a lot of non-basic infers will not be exists, Why would I rather use Flow over TS?
I think since TS support nullability checking the "inferring" part is all the "big advantage" that left to TS. If it will be only very basic, and also I can see there is a new TS like syntax for calling functions (f<number>();) It seems the gap between the two is become narrower in a way I'm sure why prefer one on the other.

@oriSomething I think specifying no type is the alternative, as far as I know, which is not very well though, Flow then autodetects? But subtypes within generic types don't have that alternative.

@lll000111 I'm not sure I understand. Can you please give a code example?

@oriSomething For what? Not specifying a type is "" (nothing) and generics syntax is generics syntax.

@lll000111 I think you can use it like this. Note Input and Response.

// @flow
import * as React from 'react';
import { type GraphQLTaggedNode } from 'react-relay';

// mutation, input, this.handleCompleted, this.handleError
export type Commit<Input, Response> = GraphQLTaggedNode => void;

const withMutation = <Props: {}, Input, Response>(
  Component: React.ComponentType<Props>,
): React.ComponentType<
  $Diff<
    Props,
    {
      commit: Commit<Input, Response> | void,
      pending: boolean | void,
    },
  >,
> => {
  return (props: Props) => (
    <Component {...props} commitMutation={42} pending={false} />
  );
};

export default withMutation;

@steida That is what I already mentioned? See my example ObjectCreation<OneVersionedObjectTypes | OneUnversionedObjectTypes | OneApplicationTypes>. I talked about this.

How would the recompose types work without *? Lots of times the * replaces a much more complex type, especially when chaining HOCs

For Recompose you can use Generics and $Call to make this work

@rj254 Just describe that type with React.ComponentType instead of HOC and flow will just work.

@sibelius not sure I follow for the example provided on the recompose site (copied here as well)

const enhance:HOC<*, EnhancedComponentProps> = compose(
  defaultProps({
    text: 'world',
  }),
  withProps(({ text }) => ({
    text: `Hello ${text}`
  }))
);

@TrySound I guess I could describe the enhanced component with React.ComponentType; however, my question was more geared to typing the enhance function.

if you wanna get the return type of enhance

you can use this helper

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

type EnhanceProps = ExtractReturn<enhance>

Guys for recompose you can type as I described here

const SomeComponentExt: React.ComponentType<EnhancedProps> = compose(
   hoc1, ..., hocN
)(SomeComponent)

export default SomeComponentExt;

I'll add this into documentation when switch on newer flow in current projects.

The only thing I need to check how to extend React classes without * as before the simplest way was to use such React.Component defiition

type Props = { ... };
class Blabla<T: *> extends React.Component<T> {
}
const BlablaExt:React.ComponentType<Props> = compose(...)(Blabla)

If anyone has idea with simple solution of above without * I'll be very appreciated for help

Ping. Still very valid. Reminder: The issue is GENERIC TYPES, where the "*" still makes sense!

It's been a while since I worked on my current code-base, and I've been bitten by this. Specifically, for Lens generation: Lens<S, A> where I can specify S but want A to be inferred. It killed the library I used, too, it seems. This is such a shame, Flow was always my preferred tool for this because of it's powerful inference -- and being able to guide it, as such.

@girvo could you try Lens<S, _> to see if it works on your example (_ can be used in places where we want that generic to be inferred)?

Was this page helpful?
0 / 5 - 0 ratings