How to annotate compose function, that will be covered on 100%. This function is polymorphic (it can accept different function types and different arguments).
/* @flow */
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
const last = funcs[funcs.length - 1];
const rest = funcs.slice(0, -1);
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args));
}
Hey @nodkz ...
This question is asked quite frequently, but quite tricky... we have an existing compose declaration for our flow-typed ramda libdef, maybe you get some inspiration here:
Hope this helps!
The gist of it: It is only possible to type this by manually writing compose declarations for a function call with 1, 2, 3,... n parameters... assuming that your users don't use a higher number than n.
@ryyppy you are crazy magician! ;)
I'm blind. I have not seen such assembler-code a long time.
PS. I want use it for graphql-compose. There must be 3 compositions (resolve methods, fields args and object types), which all works via this compose function. This compositions will be assemble via middlewares, so their number can be quite big.
Yesterday with tears in my eyes ;) I splice my lib with graphql-js definitions. And I found a lot of new things with graphql internals, that I didn't know. Thanks to flow and @leebyron type annotations.
May be somebody suggest better solution?
@nodkz For clarification, I did not write this code, I am just vary of its existence :-)
Sadly I don't use GraphQL so I can't give a qualified answer on that one...
@ryyppy no problem! Thanks again.
Gabe Levi provide his solution: (provide it here for future travelers from search engines):
/*
CAUTION
this solution has wrong order annotation.
It works for `reduce`, but in this compose function using `reduceRight`.
Proper solution can be foun胁 on next comment.
*/
/* @flow */
function composeImpl(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
const last = funcs[funcs.length - 1];
const rest = funcs.slice(0, -1);
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args));
}
var compose:
((end: void) => (<T>(x: T) => T)) &
(<Ta, Tb>(f: (x: Ta) => Tb, end: void) => ((x: Ta) => Tb)) &
(<Ta, Tb, Tc>(f1: (x: Ta) => Tb, f2: (x: Tb) => Tc, end: void) => ((x: Ta) => Tc)) &
(<Ta, Tb, Tc, Td>(f1: (x: Ta) => Tb, f2: (x: Tb) => Tc, f3: (x: Tc) => Td, end: void) => ((x: Ta) => Td)) &
(<T>(...funcs: Array<(x: T) => T>) => ((x: T) => T))
= (composeImpl: any);
type ERROR = boolean
// Explicitly handled cases work
(compose()(123) : ERROR); // number ~> boolean
(compose(String)(123) : ERROR); // string ~> boolean
(compose(String, Number)(123) : ERROR); // number ~> boolean
(compose(String, Number, String)(123) : ERROR); // string ~> boolean
// And fallback requires all the functions to take and return the same type
// which is in this case string | number
(compose(String, Number, String, Number)(123) : string | number);
Correct fully tested solution (combined from @ryyppy and @gabelevi)
/* @flow */
/* eslint-disable */
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
function composeImpl(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
const last = funcs[funcs.length - 1];
const rest = funcs.slice(0, -1);
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args));
}
type FN<A,R> = (a: A) => R;
const compose:
((end: void) => (<T>(x: T) => T)) &
(<A,B>(m1: FN<A,B>, end: void) => FN<A,B>) &
(<A,B,C>(m1: FN<B,C>, m2: FN<A,B>, end: void) => FN<A,C>) &
(<A,B,C,D>(m1: FN<C,D>, m2: FN<B,C>, m3: FN<A,B>, end: void) => FN<A,D>) &
(<A,B,C,D,E>(m1: FN<D,E>, m2: FN<C,D>, m3: FN<B,C>, m4: FN<A,B>, end: void) => FN<A,E>) &
(<A,B,C,D,E,F>(m1: FN<E,F>, m2: FN<D,E>, m3: FN<C,D>, m4: FN<B,C>, m5: FN<A,B>, end: void) => FN<A,F>) &
(<A,B,C,D,E,F,G>(m1: FN<F,G>, m2: FN<E,F>, m3: FN<D,E>, m4: FN<C,D>, m5: FN<B,C>, m6: FN<A,B>, end: void) => FN<A,G>) &
(<A,B,C,D,E,F,G,H>(m1: FN<G,H>, m2: FN<F,G>, m3: FN<E,F>, m4: FN<D,E>, m5: FN<C,D>, m6: FN<B,C>, m7: FN<A,B>, end: void) => FN<A,H>) &
(<A,B,C,D,E,F,G,H,J>(m1: FN<G,J>, m2: FN<G,H>, m3: FN<F,G>, m4: FN<E,F>, m5: FN<D,E>, m6: FN<C,D>, m7: FN<B,C>, m8: FN<A,B>, end: void) => FN<A,J>) &
(<A,B,C,D,E,F,G,H,J,K>(m1: FN<J,K>, m2: FN<G,J>, m3: FN<G,H>, m4: FN<F,G>, m5: FN<E,F>, m6: FN<D,E>, m7: FN<C,D>, m8: FN<B,C>, m9: FN<A,B>, end: void) => FN<A,K>) &
(<A,B,C,D,E,F,G,H,J,K,L>(m1: FN<K,L>, m2: FN<J,K>, m3: FN<G,J>, m4: FN<G,H>, m5: FN<F,G>, m6: FN<E,F>, m7: FN<D,E>, m8: FN<C,D>, m9: FN<B,C>, m10: FN<A,B>, end: void) => FN<A,L>) &
(<A,R>(...funcs: Array<FN<A,R>>) => FN<A,R>)
= (composeImpl: any);
export default compose;
Most helpful comment
Hey @nodkz ...
This question is asked quite frequently, but quite tricky... we have an existing
composedeclaration for our flow-typedramdalibdef, maybe you get some inspiration here:https://github.com/flowtype/flow-typed/blob/master/definitions/npm/ramda_v0.21.x/flow_%3E%3Dv0.23.x/ramda_v0.21.x.js#L36
Hope this helps!