I have the following code:
type GenericFunction<P, R> = (...args : Array<P>) => R;
let log : GenericFunction<mixed, void> = (text : string) => {
console.log(text);
}
This does not type check:
3: let log : GenericFunction<mixed, void> = (text : string) => {
^ mixed. This type is incompatible with
3: let log : GenericFunction<mixed, void> = (text : string) => {
^ string
It does work if I change mixed to any, but obviously I'd rather use mixed here...
Is there something I'm missing here?
To give a more complete example, I'm trying to create a type-checked memoize function:
type GenericFunction<P, R> = (...args : Array<P>) => R;
function memoize<P, R>(method : GenericFunction<P, R>) : GenericFunction<P, R> {
let cache : { [key : string] : R } = {};
return function() : R {
let key : string = JSON.stringify(arguments);
if (cache.hasOwnProperty(key)) {
return cache[key];
}
cache[key] = method.apply(this, arguments);
return cache[key];
};
}
export let repeat : GenericFunction<any, string> = memoize((text : string, times : number) : string => {
return new Array(times + 1).join(text);
});
And since there are mixed types in the functions I might want to run through memoize, I'd like to be able to specify GenericFunction<mixed, string> and have my code type-check.
It also doesn't seem to work with a union type:
export let repeat : GenericFunction<number | string, string> = memoize((text : string, times : number) : string => {
return new Array(times + 1).join(text);
});
23: export let repeat : GenericFunction<number | string, string> = memoize((text : string, times : number) : string => {
^ number. This type is incompatible with
23: export let repeat : GenericFunction<number | string, string> = memoize((text : string, times : number) : string => {
^ string
23: export let repeat : GenericFunction<number | string, string> = memoize((text : string, times : number) : string => {
^ string. This type is incompatible with
23: export let repeat : GenericFunction<number | string, string> = memoize((text : string, times : number) : string => {
This looks like a function argument contravariance issue here.
I'm happy to congratulate FlowType on getting this right!
Refer https://flowtype.org/blog/2016/10/04/Property-Variance.html
Glad flow is getting it right :) can you give me some pointers as to how I would type that memoize method correctly?
To figure this out one needs to realise that mixed is Flow's top type and that any is simultaneously a top type and a bottom type. (simultaneously .. sounds like quantum mechanics!)
Therefore function values assigned to the let variable in your opening post need to have the text argument contravariantly and their return type type covariantly.
Now I know this sounds like academic voodoo but hopefully this gives you a hint. While you try and go to figure it out I'll give it a go and see if I can post a solution before you do!
This type checks okay, does it solve your problem?
type GenericFunction<P, R> = (...args: Array<P>) => R;
let log: GenericFunction<string, void> = (text: string) => {
console.log(text);
}
Or more generally, this works too:
type GenericFunction<P, R> = (...args: Array<P>) => R;
let log: GenericFunction<empty, void>;
log = (arg: string) => {
console.log(arg);
}
log = (arg: number) => {
console.log(arg);
}
Working this kind of thing through leads into cool use cases for the lower type bound feature which issue I commented a few hours ago.
Lower bound of type parameter #3175
Sure, I guess that works when the function (like log, which was maybe a bad example) only has a single parameter type. But when I start trying to write types with multiple parameter types, I can only get this to type-check with any, not mixed.
So this type-checks:
type GenericFunction<P, R> = (...args : Array<P>) => R;
export let repeat : GenericFunction<any, string> = (text : string, times : number) : string => {
return new Array(times + 1).join(text);
};
But this does not:
type GenericFunction<P, R> = (...args : Array<P>) => R;
export let repeat : GenericFunction<mixed, string> = (text : string, times : number) : string => {
return new Array(times + 1).join(text);
};
3: export let repeat : GenericFunction<mixed, string> = (text : string, times : number) : string => {
^ mixed. This type is incompatible with
3: export let repeat : GenericFunction<mixed, string> = (text : string, times : number) : string => {
^ string
3: export let repeat : GenericFunction<mixed, string> = (text : string, times : number) : string => {
^ mixed. This type is incompatible with
3: export let repeat : GenericFunction<mixed, string> = (text : string, times : number) : string => {
^ number
I was thinking maybe Array<mixed> needed the same type across the array... but the following type checks:
let foo : Array<mixed> = [ 1, 'hello', { '2': 'world' } ];
But yeah, all of this is just me trying to get memoize to work, and to do that the problem I'd really like to solve here is: how do I create a type-checked function, which takes one function and returns another function with the same signature?
The simplest example I can think of this would be something like:
function proxy(method) {
return function() {
return method.apply(this, arguments);
}
}
let foo = proxy((a : string, b : number) => {
});
foo('hello', 1); // this should type-check
foo(true, 'bar'); // this should not type-check
Well... I got it to type-check correctly, with some messing around, but I'm not totally sure why it works. :)
type FunctionProxy<T : Function> = (method : T) => T;
let proxy : FunctionProxy<*> = (method : Function) : Function => {
return function() {
return method.apply(this, arguments);
};
}
let foo = proxy((a : string, b : number) => {
});
foo('hello', 1); // this should type-check
foo(true, 'bar'); // this should not type-check
Sounds like you have run up against the boundaries of what FlowType is currently able to do.
It would be really great to see a roadmap of where FlowType is going especially since the current version number indicates that it is still an infant. That would inspire more developer adoption and to stick with it for the longer haul.
[You sent your response just as I was typing mine so mine is out of sequence.]
That's a cool solution @bluepnume which you can simplify right down to:
type FunctionProxy<T: Function> = (method: T) => T;
let proxy: FunctionProxy<*> = method => () => method.apply(this, arguments);
let foo = proxy((a: string, b: number) => {
});
foo('hello', 1); // this should type-check
foo(true, 'bar'); // this should not type-check
@indiescripter
I think I have the same issue. Can you help ?
I described the faulty lines in the code below.
class Inc {
// flow cannot call 'Out.Question' because mixed [here] is incompatible with string [constructor]
static question(rawQuestion: Array<mixed>) {
return new Out.Question(...rawQuestion);
}
// But this works perfectly
static response(rawResponse: Array<any>) {
return new Out.Response(...rawResponse);
}
}
class Out {
static Question = class Question {
value: mixed;
date: string;
constructor(value: mixed, date: string) {
this.value = value;
this.date = date;
}
};
static Response = class Response {
value: mixed;
error: mixed;
date: string;
constructor(value: mixed, error: mixed, date: string) {
this.value = value;
this.error = error;
this.date = date;
}
};
}
Using Flow 0.81 with Babel 7
Hi @pirix-gh, thanks for asking this question out of the blue; A pleasant surprise email from GH!
I've not done any further FlowType development since last I commented here so that make me >18 months out of date with the current play.
The only thing that sort of stands out to me though is that your static question and static response functions are calling the respective Out.Question and Out.Response constructors using array spread (...rawQuestion and ...rawResponse) and therefore the code is in error anyway because if the rawQuestion and rawResponse arguments are empty arrays then the respective constructors will not be called with the correct number of arguments. (Out.Question constructor requires 2 arguments and Out.Response constructor requires 3 arguments).
Chances are I misunderstand what you are trying to achieve but I'm wondering why class Inc static methods are not declared more like this:
class Inc {
static question(value: mixed, date: string>) {
return new Out.Question(value, date);
}
static response(value: mixed, error: mixed, date: string) {
return new Out.Response(value, error, date);
}
}
Sorry I cannot be of more help & like I say probably I misunderstand why you are trying to do this with variable arity ES2015 array spread calls.
Thank you @indiescripter
I was just building a simple inflater-deflater to bring raw arrays of data back to an object
My mistake was, like you explained, using Arrays (that can have variable elements !)
What I was looking for is called a Tuple, and it works much better all of a sudden
I am very new to Flow so thank you for taking your time to provide me with a clear explanation
Cheers
No worries Pierre-Antoine. I hope you enjoy learning Flow.
One of the things I like about Flow is a certain amount of "academic type-theoretic goodness". Alas I found the pace of improvement of Flow (bug fixes etc) too slow for my needs (my projects could not wait) so in the end I folded and went the TypeScript route (which, alas, until recently, lacked that goodness spoken about) as I found the TS ecosystem a lot more active and faster maturing.
Still, Flow is great so, like I say, I sincerely hope you enjoy the experience.
Hi @indiescripter
After using Babel for 2 years and more recently, Flow, there is no compare with Typescript !
Setting up Typescript was done in a glance, as my code is ES2018, there only was a few breaking changes (private fields #). The compiler was setup with Webpack and it's much less of a mess than Babel, I mean more straightforward (less dependencies, less configuration...). And the compiler output also looks much cleaner !
Oh and the integration of TS with the IDE (IJ) is just sooo good, there is no coming back.
I can see my productivity increased and my code is also getting much cleaner and readable.
So thanks for convincing me, I'm moving to TS.
Cheers !
Most helpful comment
Hi @indiescripter
After using Babel for 2 years and more recently, Flow, there is no compare with Typescript !
Setting up Typescript was done in a glance, as my code is ES2018, there only was a few breaking changes (private fields #). The compiler was setup with Webpack and it's much less of a mess than Babel, I mean more straightforward (less dependencies, less configuration...). And the compiler output also looks much cleaner !
Oh and the integration of TS with the IDE (IJ) is just sooo good, there is no coming back.
I can see my productivity increased and my code is also getting much cleaner and readable.
So thanks for convincing me, I'm moving to TS.
Cheers !