Hi!
I have the following code (try link)
type A = {
type: 'a',
a: string,
};
type B = {
type: 'b',
b: number,
};
type F = ((a: A) => string) & ((b: B) => number);
const foo: F = (ab: A | B) => {
if (ab.type === 'a') {
return ab.a;
} else {
return ab.b;
}
}
I wish to make a function foo accepting a disjoint union but which returns different types for each member.
This fails with the following error:
14: return ab.a;
^ string. This type is incompatible with
11: type F = ((a: A) => string) & ((b: B) => number);
^ number
16: return ab.b;
^ number. This type is incompatible with
11: type F = ((a: A) => string) & ((b: B) => number);
^ string
What's the best way to express this type of function (disjoint types input, but different output type for each one)? I would like to not have to declare it as (ab: A|B) => string|number because that loses some information.
Sincerely,
Dan
bump
See also: #4305, #2059, #60.
I would like to see this working. Declaring a function with an intersection type works great for usages of that function, but the actual function definition has to go through any and give up type checking, or be defined in a non-flow file (same effect).
I don't think this is a bug.
The program in the original post asks whether the following subtyping rule is true:
(A|B => X|Y) <: ((A => X) & (B => Y))
The above is not true, but the reverse is true.
I agree that the subtyping relation you wrote is not true, but I think the spirit of the bug is that there's no way to write foo so that Flow infers it to have the desired type F, explicitly or implicitly (without cheating). Is that not the case? I would be overjoyed if not.
Yeah, the feature request here is to get flow to accept a real javascript function as an intersection of function types. You can _declare_ function intersections and you can cheat by first casting a function to any and then to a function intersection, but then you lose all guarantees that the function actually adheres to the intersection constraints. Flow should be able to verify that a real javascript function adheres to a function intersection type.
const foo: F = (ab) => {
if (ab.type === 'a') {
return ab.a;
} else {
return ab.b;
}
}
I noticed something interesting recently:
// @flow
interface T {
(t: number): number;
(t: string): string;
}
declare var returnTypeInterface: T;
(returnTypeInterface('string'): string);
(returnTypeInterface(2): number);
// $ExpectError
(returnTypeInterface('string'): number);
// $ExpectError
(returnTypeInterface(2): string);
(Flow Try)
This seems to work. But I'm not sure if it's safe.
@samwgoldman @mrkev can we reopen this issue?
I think an issue I'm having is related to this, so I'm asking about it here:
I'm trying to get a function to return different, but related, types based on the type of the input. For example:
type Arg = { +[string]: Object }
type CallbackArg = () => Arg
type Func = <T: Arg | CallbackArg>(T) => /* Here's where I'm stuck. */
What I'd like to do is return { +[$Keys<T>]: string } if the object is passed directly and { +[$Keys<$Call<T>>]: string} if a function is passed. Is there any way to do this type of conditional overloading? I haven't been able to find anything obvious via either the documentation or Google. I'm essentially looking for some kind of $IfCompatible<T, Arg, { +[$Keys<T>]: string }, { +[$Keys<$Call<T>>]: string }>, but a feature like that doesn't seem to exist. Am I going about this completely wrong?
For clarification, by the way, I'm dealing with this inside a type declaration file, so I can't write some kind of utility function to handle the conditional for me via ifs and a $Call<>.
I was able to get it _mostly_ working via
type Func = <T: Arg | CallbackArg>(T) => { +[$Keys<T> | $Keys<$Call<T>>]: string }
but it seems kind of awkward. I'm also not sure why this would work, as I would think Flow would complain about $Call<T> making no sense if T is Arg. For the record, using $Keys<T | $Call<T> does _not_ work.
(Try)
An interface is probably your best bet: Try
Huh. That should probably be in the documentation.
Oddly, the order seems to affect it. If the variant that takes () => T is listed in the interface definition second, it'll fail again.
Edit: And I've just noticed that @zeorin was talking about the same thing. Totally didn't notice because I didn't even think that an interface would have anything to do with this particular problem. Woops.
Interesting question! An interface is probably cleanest, I didn't realise they work that way either... good to know. I think it essentially desugars to an intersection type; this (try) is what I came up with when experimenting.
The relevant bit being:
type ObjectToString = Object => string;
type Func =
& (<T: Arg>(() => T) => $ObjMap<T, ObjectToString>)
& (<T: Arg>(T) => $ObjMap<T, ObjectToString>);
@DeedleFake wrt order, I believe they're tried left-to-right (or top-down in the case of multiple declarations in an interface I suppose), so you probably want to put more specific types first.
I've also been using intersection function types for this recently. I wish the docs mentioned this behaviour.
I figure an intersection object type is really used to check whether something is _more than one (inexact) object type at the same time_.
So for an intersection function type, it's really more than one function type at the same time. Of course, in my own code that's usually a good sign I'll be better off just creating individual functions instead, but sometimes I have to type other people's code, Redux + Redux Thunk's dispatch being a good example.
Most helpful comment
I noticed something interesting recently:
(Flow Try)
This seems to work. But I'm not sure if it's safe.