DEV Community

Cover image for Flow 0.99: Callable Properties, Function Statics, and More

Flow 0.99: Callable Properties, Function Statics, and More

Wei Gao on June 01, 2019

Cover image: Text of Time #9 Flow released 0.99 (comparing changes) a few days ago. This release contains changes around callable properties, func...
Collapse
 
yawaramin profile image
Yawar Amin

Hey thanks for the detailed explanation. A bit off-topic but, I noticed that you're modelling ActionPayload as a type alias rather than an interface. Do you have a philosophy of which to prefer, and when?

Collapse
 
wgao19 profile image
Wei Gao

Hey, Yawar, thank you for bringing this up! It just so happened that I'm recently stuck with this because we changed a shared type from interface to type alias and it broke the function calls that previously relied on the common interface. And so I'm still trying to figure out.

I also realize that things like like TOrGeneratesT (the ActionPayload in my post) do not work very well (Try Flow). I don't know how I can use interface here to help though, any pointers?

Collapse
 
yawaramin profile image
Yawar Amin

Hey in this case it seems a disjoint union would work, e.g.

type PureTOrGenT<T> =
  | {tag: 'PureT', value: T}
  | {tag: 'GenT', value: () => T}

function logMyCorn<Unicorn>(unicorn: PureTOrGenT<Unicorn>) {
  switch (unicorn.tag) {
    case 'PureT':
      console.log(unicorn.value)
      break
    case 'GenT':
      console.log(unicorn.value())
  }
}

The tag allows Flow to refine the type of value down to a T or exact function type in each branch.

My rule of thumb is to stick to interfaces unless I need the added power of unions, intersections, exact types etc. However I'm not sure what could be causing the first error you mentioned, because interfaces and inexact object types are equivalent afaik. I'll need to investigate further.

Thread Thread
 
wgao19 profile image
Wei Gao

Oh I see. And I find this approach more readable too. Can I ask -- where do you feel the boundary between flexibility and type safety lies?

For the other issue, check out this Try Flow. It works when Common is an interface. If you change Common to a type then Flow reports cannot call the second function with the parameter.

In our code, the use case is we have multiple variants of a similar object type from different APIs. And we have a lot of utility functions that rely on a few common fields of either all or some of the variants. So we're now trying to understand what's the best practice behind interface, sealed vs unsealed, exact vs inexact objects..

On a side note, seems that Flow is moving towards exact object by default. Wondering whether we're gonna make Flow very unhappy again πŸ˜…

Thread Thread
 
yawaramin profile image
Yawar Amin • Edited

It's an iterative process for me. While I'm working on a specific area I try to type everything on the 'mainline' of what I'm doing, and tell myself to come back and type out the 'side jobs' later. For example, I've recently been working with JSON validation for models, so I created a type that is essentially type Decoder<A> = Object => Promise<A>, and a constructor for this type function decoder<A>(jsonSchema: JsonSchema): Decoder<A>. For now I have type JsonSchema = Object, but I know that when I have some time I'll circle back and put in a definition that should only allow legal JSON Schemas.

Regarding the issue in Try Flow, I may be missing something, but it's not quite working like that. Even when interface Common, I'm seeing an error on this line: obj.missingProperty && console.log(obj.missingProperty); // error

The error is:

    17:   obj.missingProperty && console.log(obj.missingProperty);  // error
          ^ all branches are incompatible: Either property `missingProperty` is missing in `Common` [1]. Or property `missingProperty` is missing in object type [2].
        References:
        6: type HasExtra = Common & {
                           ^ [1]
        6: type HasExtra = Common & {
                                    ^ [2]

That looks about right, missingProperty is not mentioned in any of the type definitions so I would not expect it to work. When that is commented out though I am surprised that this works: takesExtra(obj) because we know obj: Common but takesExtra takes obj: HasExtra. Effectively Flow is having to downcast a supertype to a subtype to report no errors?!

Wow, the planned Flow inexact/exact type switch is going to be a big one. I've asked on that post, but I have a feeling that interfaces will not be affected, i.e. they will continue to be 'inexact'. My tendency is to use interfaces as much as possible; sometimes, there's just no point in giving a sub-object type a name so I directly use an object type {foo: bar}. I'm not sure I understand the post's argument that this being an inexact type makes it unsafe. I know there are some cases where you just don't want to allow passing in extra properties but IMHO it would be more annoying than unsafe. I have some examples in a post I wrote, dev.to/yawaramin/interfaces-for-sc...

Thread Thread
 
wgao19 profile image
Wei Gao • Edited

Oops, sorry I think my annotation on the previous flow try was confusing.

The previous link works as expected, missingProperty is expecting error because it is not mentioned, and c is OK because it is annotated as optional in HasExtra. However, once we change interface to type, while both c and missingProperty still work as expected, there is an error on line 12 that does not let us pass the common object.

I will definitely need to read your post on interface. Thanks for sharing! Nobody in our team has any experience with it and Flow's doc doesn't say much about it neither πŸ˜…

Thread Thread
 
yawaramin profile image
Yawar Amin

Oh I see what you mean now–and why the interface type works! Because an object with type:

interface Common {
  a: number,
  b: string,
}

... can always be safely downcast to its subtype type HasExtra = Common & {c?: string}. In fact now I'm surprised the type version doesn't work, ha ha. In any case, yeah I recommend to looking at interfaces more heavily. The doc page doesn't mention this but they can be used to model the shapes of not just classes but also 'POJOs', just like in TypeScript. This gives them quite a wide variety of use cases.

Thread Thread
 
wgao19 profile image
Wei Gao

So, it turns out that (from my understanding) interfaces allow structural subtyping which distincts themselves from types.

Although, I run into this error that I don't quite understand, but looks interesting..

Thread Thread
 
yawaramin profile image
Yawar Amin

Wow, yeah that's a head-scratcher. No idea what's happening there!